Ошибка пула подключения с объектами ActiveRecord в rufus-scheduler
Я использую rufus-scheduler для запуска ряда частых заданий, которые выполняют некоторые различные задачи с объектами ActiveRecord. Если есть какой-либо сетевой или postgresql hiccup, даже после восстановления, все потоки будут вызывать следующую ошибку до перезапуска процесса:
ActiveRecord:: ConnectionTimeoutError (не удалось получить соединение с базой данных в течение 5 секунд (дождался 5.000122687 секунд). Максимальный размер пула в настоящее время равен 5, подумайте об увеличении его.
Ошибка может быть легко воспроизведена путем перезапуска postgres. Я пробовал играть (до 15) с размером пула, но вам не повезло.
Это заставляет меня полагать, что соединения находятся в простом состоянии, которое, как я думал, будет исправлено с вызовом clear_stale_cached_connections!
.
Есть ли более надежный шаблон для этого?
Блок, который передается, представляет собой простой вызов выбора и обновления активной записи, и, случается, имеет значение, что такое объект AR.
Работа rufus:
scheduler.every '5s' do
db do
DataFeed.update #standard AR select/update
end
end
обертка:
def db(&block)
begin
ActiveRecord::Base.connection_pool.clear_stale_cached_connections!
#ActiveRecord::Base.establish_connection # this didn't help either way
yield block
rescue Exception => e
raise e
ensure
ActiveRecord::Base.connection.close if ActiveRecord::Base.connection
ActiveRecord::Base.clear_active_connections!
end
end
Ответы
Ответ 1
Планировщик Rufus запускает новый поток для каждой работы.
ActiveRecord, с другой стороны, не может обмениваться соединениями между потоками, поэтому ему необходимо назначить подключение к определенному потоку.
Когда ваш поток еще не подключен, он получит один из пула.
(Если все соединения в пуле используются, он будет ждать до тех пор, пока один из них не будет возвращен из другого потока. В конечном итоге тайм-аут и выброс ConnectionTimeoutError)
Это ваша обязанность вернуть его обратно в пул, когда вы закончите с ним, в приложении Rails это делается автоматически. Но если вы управляете своими потоками (как это делает rufus), вы должны сделать это сами.
Уныло, для этого есть api:
Если вы поместите свой код внутри блока with_connection, он получит соединение из пула и освободит его, когда это будет сделано.
ActiveRecord::Base.connection_pool.with_connection do
#your code here
end
В вашем случае:
def db
ActiveRecord::Base.connection_pool.with_connection do
yield
end
end
Должен сделать трюк....
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html#method-i-with_connection
Ответ 2
Причина может заключаться в том, что у вас много потоков, которые используют все подключения, если метод DataFeed.update
занимает более 5 секунд, чем ваш блок может перекрываться.
попробуйте
scheduler.every("5s", :allow_overlapping => false) do
#...
end
Также попробуйте освободить соединение, а не закрывать его.
ActiveRecord::Base.connection_pool.release_connection
Ответ 3
Я действительно не знаю о rufus-scheduler, но у меня есть некоторые идеи.
Первой проблемой может быть ошибка в rufus-scheduler, которая неправильно проверяет соединение с базой данных. Если это случай, единственным решением является очистка устаревших подключений вручную, как вы уже это сделали, и сообщить автору rufus-scheduler о вашей проблеме.
Другая проблема, которая может произойти, заключается в том, что ваша операция DataFeed занимает очень много времени и потому, что она выполняется каждые 5 секунд. У Rails заканчивается соединение с базой данных, но это маловероятно.