SQL通知を接続単位で無効化できる `sql_notifications` オプションを追加
Active Record の SQL 通知をデータベース接続ごとに無効化できる sql_notifications オプションが追加され、不要な計測オーバーヘッドを抑制できるようになりました。設定を false にすれば、対象接続からは sql.active_record イベントが一切発行されません。
背景
Solid Cache や Solid Queue など、特定のデータベース接続で Active Record の計測が不要になるケースが増えていました。従来は内部の instrumenter を置き換えるパッチを当て、すべての呼び出し元で disable_instrumentation ラップを行うという、侵入度の高い手法が取られていました。
その手法は内部実装に依存し、コードベース全体に散らばるラップ処理が保守コストを上げる原因となっていました。接続単位で簡潔に無効化できる設定が求められ、今回の PR で実装されました。
この背景から、設定だけで通知抑制が可能になる という点が本変更の主旨です。
技術的な変更
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb に sql_notifications 設定項目が追加され、デフォルトは true です。`
@@
- @config.fetch(:advisory_locks, true)
+ @config.fetch(:advisory_locks, true)
+ )
+
+ @sql_notifications = self.class.type_cast_config_to_boolean(
+ @config.fetch(:sql_notifications, true)
+ )
この設定は sql_notifications? メソッドで外部から参照でき、接続インスタンスが通知を行うか判定します。
@@
- def prepared_statements_disabled_cache # :nodoc:
+ def sql_notifications?
+ @sql_notifications
+ end
ActiveRecord::ConnectionAdapters::AbstractAdapter#instrumenter は、sql_notifications? が false の場合に ActiveSupport::Notifications.null_instrumenter を返すよう変更されました。
@@
- ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter
+ if sql_notifications?
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] ||= ActiveSupport::Notifications.instrumenter
+ else
+ ActiveSupport::Notifications.null_instrumenter
+ end
非同期クエリ用の EventBuffer も同様に sql_notifications? が真の場合にだけ生成されます。これにより、通知が無効化された接続ではイベントバッファも不要となります。
@@
- @event_buffer = EventBuffer.new(self, ActiveSupport::Notifications.instrumenter)
- ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
+ if connection.sql_notifications?
+ @event_buffer = EventBuffer.new(self, ActiveSupport::Notifications.instrumenter)
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
+ end
activesupport 側にも NullInstrumenter クラスが新規追加され、ActiveSupport::Notifications.null_instrumenter がこのシングルトンを返します。instrument はブロックをそのまま実行し、通知は一切発行しません。
@@
+ class NullInstrumenter
+ class NullHandle; def start; end; def finish; end; end
+ NULL_HANDLE = NullHandle.new.freeze
+ def instrument(name, payload = {}); yield payload if block_given?; end
+ def build_handle(name, payload); NULL_HANDLE; end
+ def start(name, payload); end; def finish(name, payload); end
+ end
テストは activerecord/test/cases/adapter_test.rb に追加され、sql_notifications: false の接続で sql.active_record 通知が空であること、デフォルトは通知が出ることを検証しています。
これらの変更は デフォルト動作を維持しつつ、オプションだけで通知抑制を可能にする という最小侵入の設計です。
設計判断
設定キーは既存の sql_notifications を拡張し、新規キーを導入しない方針が採られました。デフォルトは true で、既存アプリへの影響はありません。これにより後方互換性が確保されています。
NullInstrumenter はステートレスかつスレッドセーフな単一インスタンスとして実装され、Per‑thread の instrumenter レジストリを回避 できる点がメリットです。通知無効化のために余計なオブジェクトを生成しない設計は、リソース節約にもつながります。
非同期クエリのバッファリング処理も sql_notifications? が偽の場合はスキップされます。イベントバッファ自体が不要になることで、メモリ使用量と CPU 負荷が低減されることが期待できます。
以上の判断は、「機能追加は最小限のコード変更で、設定だけで有効/無効を切り替えられる」 という Rails の設計哲学に沿ったものです。
まとめ
sql_notifications 接続オプションにより、データベース接続ごとに SQL 通知の有無を制御できるようになり、Solid Cache や Solid Queue などの内部計測が不要なケースでオーバーヘッドを除去できます。NullInstrumenter によるノーオペレーション実装と、デフォルト互換性を保つ設計が本変更の核心です。