From 3b32d981bafe3b58e34b27e2ef0a181c51d0469d Mon Sep 17 00:00:00 2001 From: Matt Cruz Date: Tue, 24 Dec 2024 09:16:54 -0500 Subject: [PATCH 1/4] Add ActiveRecordProxyAdapters::LogSubscriber --- CHANGELOG.md | 1 + docker-compose.yml | 1 + .../configuration.rb | 26 ++++++++++-- .../log_subscriber.rb | 42 +++++++++++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 lib/active_record_proxy_adapters/log_subscriber.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index cc3fcc3..a7478f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [Unreleased] +- Add custom log subscriber to tag queries based on the adapter being used - Fix replica connection pool getter when database configurations have multiple replicas - Retrieve replica pool without checking out a connection diff --git a/docker-compose.yml b/docker-compose.yml index 255f5a1..23c4495 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,6 +50,7 @@ services: - POSTGRES_LOG_STATEMENT=${POSTGRES_LOG_STATEMENT:-} - REPLICA_USER=replicator - REPLICA_PASSWORD=replicator + container_name: postgres_primary environment: POSTGRES_DB: postgres POSTGRES_USER: postgres_primary_test diff --git a/lib/active_record_proxy_adapters/configuration.rb b/lib/active_record_proxy_adapters/configuration.rb index 7395b12..d1a65b6 100644 --- a/lib/active_record_proxy_adapters/configuration.rb +++ b/lib/active_record_proxy_adapters/configuration.rb @@ -5,8 +5,10 @@ module ActiveRecordProxyAdapters # Provides a global configuration object to configure how the proxy should behave. class Configuration - PROXY_DELAY = 2.seconds.freeze - CHECKOUT_TIMEOUT = 2.seconds.freeze + PROXY_DELAY = 2.seconds.freeze + CHECKOUT_TIMEOUT = 2.seconds.freeze + LOG_SUBSCRIBER_PRIMARY_PREFIX = proc { |event| "#{event.payload[:connection].class::ADAPTER_NAME} Primary" }.freeze + LOG_SUBSCRIBER_REPLICA_PREFIX = proc { |event| "#{event.payload[:connection].class::ADAPTER_NAME} Replica" }.freeze # @return [ActiveSupport::Duration] How long the proxy should reroute all read requests to the primary database # since the latest write. Defaults to PROXY_DELAY. @@ -15,9 +17,25 @@ class Configuration # Defaults to CHECKOUT_TIMEOUT. attr_accessor :checkout_timeout + # @return [Proc] Prefix for the log subscriber when the primary database is used. + attr_reader :log_subscriber_primary_prefix + + # @return [Proc] Prefix for the log subscriber when the replica database is used. + attr_reader :log_subscriber_replica_prefix + def initialize - self.proxy_delay = PROXY_DELAY - self.checkout_timeout = CHECKOUT_TIMEOUT + self.proxy_delay = PROXY_DELAY + self.checkout_timeout = CHECKOUT_TIMEOUT + self.log_subscriber_primary_prefix = LOG_SUBSCRIBER_PRIMARY_PREFIX + self.log_subscriber_replica_prefix = LOG_SUBSCRIBER_REPLICA_PREFIX + end + + def log_subscriber_primary_prefix=(prefix) + @log_subscriber_primary_prefix = prefix.is_a?(Proc) ? prefix : proc { prefix.to_s } + end + + def log_subscriber_replica_prefix=(prefix) + @log_subscriber_replica_prefix = prefix.is_a?(Proc) ? prefix : proc { prefix.to_s } end end end diff --git a/lib/active_record_proxy_adapters/log_subscriber.rb b/lib/active_record_proxy_adapters/log_subscriber.rb new file mode 100644 index 0000000..4b978b7 --- /dev/null +++ b/lib/active_record_proxy_adapters/log_subscriber.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecordProxyAdapters + class LogSubscriber < ActiveRecord::LogSubscriber # rubocop:disable Style/Documentation + attach_to :active_record + + IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze + + def sql(event) + payload = event.payload + name = payload[:name] + unless IGNORE_PAYLOAD_NAMES.include?(name) + name = [database_instance_prefix_for(event), name].compact.join(" ") + payload[:name] = name + end + super(event) + end + + protected + + def database_instance_prefix_for(event) + connection = event.payload[:connection] + config = connection.instance_variable_get(:@config) + prefix = if config[:replica] || config["replica"] + log_subscriber_replica_prefix + else + log_subscriber_primary_prefix + end + + "[#{prefix.call(event)}]" + end + + private + + delegate :config, to: :ActiveRecordProxyAdapters + delegate :log_subscriber_primary_prefix, :log_subscriber_replica_prefix, to: :config + + def config + ActiveRecordProxyAdapters.config + end + end +end From 3093b9157034959cd5c6536160e0ec8d73464f27 Mon Sep 17 00:00:00 2001 From: Matt Cruz Date: Tue, 24 Dec 2024 11:32:40 -0500 Subject: [PATCH 2/4] Fix Rubocop offenses --- lib/active_record_proxy_adapters/log_subscriber.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record_proxy_adapters/log_subscriber.rb b/lib/active_record_proxy_adapters/log_subscriber.rb index 4b978b7..1b5a532 100644 --- a/lib/active_record_proxy_adapters/log_subscriber.rb +++ b/lib/active_record_proxy_adapters/log_subscriber.rb @@ -13,7 +13,7 @@ def sql(event) name = [database_instance_prefix_for(event), name].compact.join(" ") payload[:name] = name end - super(event) + super end protected From 407a63478aad4c0c569fbc1e9bbf24e315d2e68e Mon Sep 17 00:00:00 2001 From: Matt Cruz Date: Tue, 24 Dec 2024 11:36:36 -0500 Subject: [PATCH 3/4] Refactor --- lib/active_record_proxy_adapters/log_subscriber.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/active_record_proxy_adapters/log_subscriber.rb b/lib/active_record_proxy_adapters/log_subscriber.rb index 1b5a532..fbfa66a 100644 --- a/lib/active_record_proxy_adapters/log_subscriber.rb +++ b/lib/active_record_proxy_adapters/log_subscriber.rb @@ -32,7 +32,6 @@ def database_instance_prefix_for(event) private - delegate :config, to: :ActiveRecordProxyAdapters delegate :log_subscriber_primary_prefix, :log_subscriber_replica_prefix, to: :config def config From ede5933af255aa2b226e4a476235687ed078e90f Mon Sep 17 00:00:00 2001 From: Matt Cruz Date: Tue, 24 Dec 2024 11:37:53 -0500 Subject: [PATCH 4/4] Require log subscriber in unit tests --- spec/spec_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 76ab835..d441c7c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -30,9 +30,11 @@ require "active_record_proxy_adapters" require "active_record_proxy_adapters/connection_handling" +require "active_record_proxy_adapters/log_subscriber" require_relative "test_helper" ActiveRecord::Base.extend ActiveRecordProxyAdapters::ConnectionHandling +ActiveRecord::Base.logger = Logger.new(Tempfile.create) ENV["RAILS_ENV"] ||= TestHelper.env_name