Skip to content

Commit

Permalink
ActiveRecord: Improve find_db_config performance
Browse files Browse the repository at this point in the history
I noticed an application spending ~5ms on `find_db_config`, with a lot
of time spent on sorting the database configs and comparing the arrays
(`Array#<=>`). I updated `find_db_config` to avoid the sort entirely.

Here is a benchmark script I used to measure the improvement:

```

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", github: "rails/rails", branch: "main"
  gem "benchmark-ips"
end

require "active_record"
require "active_record/database_configurations"

module ActiveRecord
  class DatabaseConfigurations
    def fast_find_db_config(env)
      current_env_configs, other_configs = configurations.partition(&:for_current_env?)
      [*current_env_configs, *other_configs].find do |db_config|
        db_config.env_name == env.to_s ||
          (db_config.for_current_env? && db_config.name == env.to_s)
      end
    end
  end
end

def generate_configs(adapter, count)
  count.times.to_h do |i|
    [i == 0 ? "primary" : "config_#{i}", { "adapter" => adapter }]
  end
end

small_config = ActiveRecord::DatabaseConfigurations.new(
  **generate_configs("sqlite3", 3)
)

large_config = ActiveRecord::DatabaseConfigurations.new({
  **generate_configs("randomadapter", 100),
  ActiveRecord::ConnectionHandling::DEFAULT_ENV.call => generate_configs("sqlite3", 100)
})

SCENARIOS = {
  "Empty"                   => ActiveRecord::DatabaseConfigurations.new({}),
  "A few connections"       => small_config,
  "Hundreds of connections" => large_config,
}

SCENARIOS.each_pair do |name, value|
  puts
  puts " #{name} ".center(80, "=")
  puts

  Benchmark.ips do |x|
    x.report("find_db_config") { value.find_db_config("primary") }
    x.report("fast_find_db_config") { value.fast_find_db_config("primary") }
    x.compare!
  end
end
```

The results show a consistent speedup, especially for many configs:

==================================== Empty =====================================

Warming up --------------------------------------
      find_db_config    82.849k i/100ms
 fast_find_db_config   172.141k i/100ms
Calculating -------------------------------------
      find_db_config    830.202k (± 1.9%) i/s -      4.225M in   5.091388s
 fast_find_db_config      1.633M (± 6.6%) i/s -      8.263M in   5.082794s

Comparison:
 fast_find_db_config:  1633426.8 i/s
      find_db_config:   830201.9 i/s - 1.97x  slower

============================== A few connections ===============================

Warming up --------------------------------------
      find_db_config    25.356k i/100ms
 fast_find_db_config    47.260k i/100ms
Calculating -------------------------------------
      find_db_config    248.648k (± 2.7%) i/s -      1.268M in   5.102833s
 fast_find_db_config    475.184k (± 3.0%) i/s -      2.410M in   5.077268s

Comparison:
 fast_find_db_config:   475184.1 i/s
      find_db_config:   248647.6 i/s - 1.91x  slower

=========================== Hundreds of connections ============================

Warming up --------------------------------------
      find_db_config   361.000  i/100ms
 fast_find_db_config     2.400k i/100ms
Calculating -------------------------------------
      find_db_config      3.622k (± 1.9%) i/s -     18.411k in   5.085694s
 fast_find_db_config     24.073k (± 2.0%) i/s -    122.400k in   5.086726s

Comparison:
 fast_find_db_config:    24073.0 i/s
      find_db_config:     3621.5 i/s - 6.65x  slower
  • Loading branch information
alexcwatt committed Apr 8, 2023
1 parent 1aac485 commit d057069
Showing 1 changed file with 6 additions and 6 deletions.
12 changes: 6 additions & 6 deletions activerecord/lib/active_record/database_configurations.rb
Expand Up @@ -119,12 +119,12 @@ def configs_for(env_name: nil, name: nil, config_key: nil, include_hidden: false
# If the application has multiple databases +find_db_config+ will return
# the first DatabaseConfig for the environment.
def find_db_config(env)
configurations
.sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] }
.find do |db_config|
db_config.env_name == env.to_s ||
(db_config.for_current_env? && db_config.name == env.to_s)
end
env = env.to_s
configurations.find do |db_config|
db_config.for_current_env? && (db_config.env_name == env || db_config.name == env)
end || configurations.find do |db_config|
db_config.env_name == env
end
end

# A primary configuration is one that is named primary or if there is
Expand Down

0 comments on commit d057069

Please sign in to comment.