2013-05-27 4 views
14

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

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

Но it seemsActiveRecord::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 

я запускаю этот скрипт с 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)

ответ

10

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

После прочтения tenderlove's post о подключении в ActiveRecord, в котором объясняется, как иерархия классов излишне связана с управлением подключением, я понял, почему делать то, что я пытаюсь сделать, не так прямолинейно, как можно было бы ожидать.

Что я в конечном итоге делаю был подклассы ActiveRecord-х ConnectionHandler и используя этот новый обработчик соединения в верхней части моей модели иерархии (некоторые пустячный на ConnectionHandler code был необходим, чтобы понять, как это работает внутри, поэтому, конечно, это решение может быть очень привязанный к версии 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's 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 предложить там.

+0

@MikeCampbell, я не сталкивался с этой проблемой при ее реализации. Это решение предназначалось для сайта с чем-то вроде дюжины поддоменов, а не сотен, и это число не ожидалось. Если вам нужно удержать количество подключенных подключений, возможно, вы можете использовать какую-либо другую структуру данных для хранения пулов соединений, например кеш LRU с максимальным размером. – epidemian

+0

Вижу, спасибо за ответ! –

+0

И для рельсов 4? – Rubytastic

0

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

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

+0

Interesting; возможно, я пытаюсь решить проблему no-problem = P. У вас есть указатель на эту информацию? Основные тесты, которые я выполнил (в основном, цикл запроса cURL, ударяющего по веб-серверу (Unicorn) с разными субдоменами (то есть разными БД), показывают, что при сохранении пулов подключений происходит довольно значительное сокращение времени ожидания. – epidemian

+0

Для анонимного downvoter: Пожалуйста, сообщите нам, почему информация в этом ответе не полезна. –

+0

Мастер-процесс Unicorn содержит пул соединений, в котором рабочие проверяют соединение с момента запуска работника. См. Https://devcenter.heroku.com/articles/concurrency-and-database-connections – Rafe

Смежные вопросы