PostgreSQL・MySQLアダプターの接続設定を一元化し、個別スキップを可能に
RailsのPostgreSQLおよびMySQLアダプターで、接続時のSET文発行ロジックが刷新されました。設定値をfalseにすることで個別の設定をスキップできるようになり、ロードバランサーやプロキシ経由の接続環境での制御が容易になります。
背景
データベース接続時にRailsが発行するSET文は、ロードバランサーやコネクションプーリングプロキシ(PgBouncerなど)を経由する環境では問題を引き起こすことがありました。これらの中間層はセッションレベルの設定変更を適切に扱えない場合があり、アプリケーション側でSET文の発行自体を抑制したいというニーズがありました。
また、関連PR #57013(PostgreSQLの既知OIDを事前定義する取り組み)と合わせて、接続時のクエリ数を削減するための基盤整備も背景にあります。個々のSET文をPostgreSQLの parameter_status で検査し、サーバーがすでに期待値を持っている場合はSET文そのものをスキップする仕組みが求められていました。
技術的な変更
PostgreSQLアダプターでは、configure_connectionメソッドが大幅にリファクタリングされ、すべての設定項目をハッシュに集約してから internal_set_config で一括適用する方式に変わりました。
変更前:
def configure_connection
if @config[:encoding]
@raw_connection.set_client_encoding(@config[:encoding])
end
self.client_min_messages = @config[:min_messages] || "warning"
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
# ...
set_standard_conforming_strings
variables = @config.fetch(:variables, {}).stringify_keys
query_command("SET intervalstyle = iso_8601", "SCHEMA")
variables.map do |k, v|
# ...
end
end
変更後:
def configure_connection
if @config[:encoding]
@raw_connection.set_client_encoding(@config[:encoding])
end
# Build a single hash of all settings we want to configure, then
# set them all through internal_set_config which can skip redundant
# SETs by checking parameter_status.
settings = {}
settings["standard_conforming_strings"] = "on" unless @config[:standard_conforming_strings] == false
settings["IntervalStyle"] = "iso_8601" unless @config[:intervalstyle] == false
unless @config[:min_messages] == false
settings["client_min_messages"] = @config[:min_messages] || "warning"
end
@config.fetch(:variables, {}).stringify_keys.each do |k, v|
# ...
end
end
internal_set_configはPostgreSQLのparameter_statusをチェックし、サーバーがすでに期待値を保持している場合はSET文の発行をスキップします。これにより、接続確立時の不要なクエリが削減されます。
schema_search_path= メソッドにも同様の最適化が加わりました。
def schema_search_path=(schema_csv)
return if schema_csv == @schema_search_path
if schema_csv
# Check parameter_status to skip redundant SET when the server
# already has the desired search_path (e.g. on initial connection).
current = with_raw_connection(materialize_transactions: false) { |conn| conn.parameter_status("search_path") }
unless current == schema_csv
query_command("SET search_path TO #{schema_csv}", "SCHEMA")
end
@schema_search_path = schema_csv
end
end
MySQLアダプターでは、wait_timeout と sql_mode の2つの設定がスキップ可能になりました。wait_timeout: falseを指定するとデフォルト値(2,147,483秒)の上書きが行われず、サーバー側のデフォルト値が維持されます。sql_modeについては、variables: { sql_mode: false }またはvariables: { sql_mode: :default }で既存の:default挙動と一貫した形でスキップできます。
# 変更前: 常に wait_timeout を設定
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
variables["wait_timeout"] = wait_timeout
# 変更後: false の場合はスキップ
unless @config[:wait_timeout] == false
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
variables["wait_timeout"] = wait_timeout
end
あわせて、set_standard_conforming_stringsメソッドが非推奨(deprecated)になりました。このメソッドが担っていた処理は統合された設定ハッシュを通じて自動的に処理されるようになったためです。
def set_standard_conforming_strings
internal_set_config("standard_conforming_strings", "on")
end
deprecate :set_standard_conforming_strings, deprecator: ActiveRecord.deprecator
設計判断
設定のスキップにnilではなく false を使う規約を採用した点が特徴的です。nilは「設定なし(デフォルト値を使う)」を意味することが多く、Railsの設定体系では一般的に省略可能な値として扱われます。falseを「明示的に無効化する」というセマンティクスに割り当てることで、「設定し忘れ」と「意図的なスキップ」を区別できます。
また、すべての設定を一つのループで処理する方式への統一は、parameter_statusによるスキップロジックを共通化するための基盤となっています。各設定を個別のメソッド呼び出しに分散させていた従来の構造では、このような横断的な最適化を適用しにくかったと考えられます。MySQLのsql_modeについては、既存の:defaultによる挙動との一貫性を保つため、falseと:defaultの両方をスキップ条件として受け付ける設計になっています。
まとめ
この変更は、単なるリファクタリングにとどまらず、ロードバランサーやプロキシ経由の接続という実運用上の課題に対する明示的な解を提供します。parameter_statusを活用したSET文の冗長性排除と、falseによる設定スキップの組み合わせにより、接続時のクエリを最小化しながら運用環境に応じた細粒度の制御が可能になりました。