DEV: Adds a freedom patch to cache connection type map (#25923)

Why this change?

This patch has been added to address the problems identified in https://github.com/rails/rails/issues/35311. For every,
new connection created using the PostgreSQL adapter, 3 queries are executed to fetch type map information from the `pg_type`
system catalog, adding about 1ms overhead to every connection creation.

On multisite clusters where connections are reaped more aggressively, the 3 queries executed
accounts for a significant portion of CPU usage on the PostgreSQL cluster. This patch works around the problem by
caching the type map in a class level attribute to reuse across connections.
This commit is contained in:
Alan Guo Xiang Tan 2024-02-28 14:29:13 +08:00 committed by GitHub
parent 9a07a9872f
commit 408d2f8e69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 0 deletions

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
# This patch has been added to address the problems identified in https://github.com/rails/rails/issues/35311. For every,
# new connection created using the PostgreSQL adapter, 3 queries are executed to fetch the type map adding about 1ms overhead
# to every connection creation. In multisite clusters where connections are reaped more aggressively, the 3 queries executed
# accounts for a significant portion of CPU usage on the PostgreSQL cluster. This patch works around the problem by
# caching the type map in a class level attribute to reuse across connections.
#
# The latest attempt to fix the problem in Rails is in https://github.com/rails/rails/pull/46409 but it has gone stale.
module FreedomPatches
module PostgreSQLAdapter
# Definition as of writing: https://github.com/rails/rails/blob/5bf5344521a6f305ca17e0004273322a0a26f50a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L614
def initialize_type_map(m = type_map)
if !self.class.type_map.nil?
@type_map = self.class.type_map
else
super.tap { self.class.type_map = @type_map }
end
end
end
module PostgreSQLAdapterClassMethods
extend ActiveSupport::Concern
included do
@type_map_mutex = Mutex.new
@type_map = nil
def self.type_map
@type_map_mutex.synchronize { @type_map }
end
def self.type_map=(type_map)
@type_map_mutex.synchronize { @type_map = type_map }
end
end
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(FreedomPatches::PostgreSQLAdapter)
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include(
FreedomPatches::PostgreSQLAdapterClassMethods,
)
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
RSpec.describe "Caching PostgreSQL connection type map" do
it "caches the type map and avoid querying the database for type map information on every new connection" do
expect(ActiveRecord::Base.connection.class.type_map).to be_present
pg_type_queries = []
subscriber =
ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
if payload[:name] == "SCHEMA"
sql = payload[:sql]
pg_type_queries.push(sql) if sql.include?("pg_type")
end
end
expect do ActiveRecord::Base.connection.reconnect! end.not_to change { pg_type_queries.length }
ensure
ActiveSupport::Notifications.unsubscribe(subscriber)
end
end