Подтвердить что ты не робот

Ошибка пула подключения с объектами 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
4b9b3361

Ответ 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 заканчивается соединение с базой данных, но это маловероятно.