Rails: переключать соединение по каждому запросу, но поддерживать пул соединений - программирование
Подтвердить что ты не робот

Rails: переключать соединение по каждому запросу, но поддерживать пул соединений

В нашем Rails-приложении нам нужно использовать разные базы данных в зависимости от субдомена запроса (разные БД для каждой страны).

Сейчас мы делаем что-то похожее на то, что рекомендуется в на этот вопрос. То есть, вызов ActiveRecord::Base.establish_connection для каждого запроса.

Но кажется ActiveRecord::Base.establish_connection удаляет текущий пул соединений и устанавливает новое соединение каждый раз, когда он вызывается.

Я сделал этот быстрый тест, чтобы узнать, существует ли какая-либо существенная разница между вызовом establish_connection каждый раз и с уже установленными соединениями:

require 'benchmark/ips'

$config = Rails.configuration.database_configuration[Rails.env]
$db1_config = $config.dup.update('database' => 'db1')
$db2_config = $config.dup.update('database' => 'db2')

# Method 1: call establish_connection on each "request".
Benchmark.ips do |r|
  r.report('establish_connection:') do
    # Simulate two requests, one for each DB.
    ActiveRecord::Base.establish_connection($db1_config)
    MyModel.count # A little query to force the DB connection to establish.
    ActiveRecord::Base.establish_connection($db2_config)
    MyModel.count
  end
end

# Method 2: Have different subclasses of my models, one for each DB, and 
# call establish_connection only once
class MyModelDb1 < MyModel
  establish_connection($db1_config)
end

class MyModelDb2 < MyModel
  establish_connection($db2_config)
end

Benchmark.ips do |r|
  r.report('different models:') do
    MyModelDb1.count
    MyModelDb2.count
  end
end

Я запускаю этот script с rails runner и указывая на локальный mysql с несколькими тысячами записей в БД, и результаты, похоже, указывают на то, что на самом деле это довольно большая разница (на порядок) между два метода (BTW, я не уверен, что эталонный показатель действителен или я прикручен, и поэтому результаты вводят в заблуждение):

Calculating -------------------------------------
establish_connection: 8 i/100ms
-------------------------------------------------
establish_connection: 117.9 (±26.3%) i/s -        544 in   5.001575s
Calculating -------------------------------------
    different models:  119 i/100ms
-------------------------------------------------
    different models:  1299.4 (±22.1%) i/s -       6188 in   5.039483s

Итак, в основном, я хотел бы знать, есть ли способ поддерживать пул соединений для каждого субдомена, а затем повторно использовать эти соединения, а не устанавливать новое соединение для каждого запроса. Подкласс моих моделей для каждого поддомена невозможен, так как существует множество моделей; я просто хочу изменить соединение для всех моделей (в ActiveRecord::Base)

4b9b3361

Ответ 1

Ну, я уже немного вникал в это и сумел заставить что-то работать.

После прочтения ласкового сообщения об управлении соединением в ActiveRecord, в котором объясняется, как иерархия классов становится ненужной в сочетании с управлением подключением, я понял, почему что я пытаюсь сделать не так просто, как можно было бы ожидать.

То, что я закончил, было подклассификация ActiveRecord ConnectionHandler и использование этого нового обработчика соединений в верхней части моей иерархии моделей (некоторые из них для того, чтобы понять, как он работает внутри, нужен ConnectionHandler код, поэтому, конечно, это решение может быть очень привязано к версии Rails, которую я использую (3.2 )). Что-то вроде:

# A model class that connects to a different DB depending on the subdomain 
# we're in
class ModelBase < ActiveRecord::Base
  self.abstract_class = true
  self.connection_handler = CustomConnectionHandler.new
end

# ...

class CustomConnectionHandler < ActiveRecord::ConnectionAdapters::ConnectionHandler
  def initialize
    super
    @pools_by_subdomain = {}
  end

  # Override the behaviour of ActiveRecord ConnectionHandler to return a
  # connection pool for the current domain.
  def retrieve_connection_pool(klass)
    # Get current subdomain somehow (Maybe store it in a class variable on 
    # each request or whatever)
    subdomain = @@subdomain
    @pools_by_subdomain[subdomain] ||= create_pool(subdomain)
  end

  private
  def create_pool(subdomain)
    conf = Rails.configuration.database_configuration[Rails.env].dup
    # The name of the DB for that subdomain...
    conf.update!('database' => "db_#{subdomain}")
    resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(conf, nil)
    # Call ConnectionHandler#establish_connection, which receives a key 
    # (in this case the subdomain) for the new connection pool
    establish_connection(subdomain, resolver.spec)
  end
end

Это все еще нуждается в тестировании, чтобы проверить, действительно ли есть прирост производительности, но мои начальные тесты, запущенные на локальном сервере Unicorn, предполагают, что есть.

Ответ 2

Насколько я знаю, Rails не поддерживает этот пул базы данных между запросами, за исключением случаев использования многопоточного env. как Sidekiq. Но если вы используете Passenger или Unicorn на своем производственном сервере, он создаст новое соединение с базой данных для каждого экземпляра Rails.

Таким образом, в основном использование пула подключений к базе данных бесполезно, а это значит, что создание нового подключения к базе данных по каждому запросу не должно вызывать беспокойства.