Sinatra, Puma, ActiveRecord: не найден пул соединений с «основным» - PullRequest
0 голосов
/ 12 января 2019

Я создаю сервис в Ruby 2.4.4, с Sinatra 2.0.5, ActiveRecord 5.2.2, Puma 3.12.0. (Я не использую рельсы.)

Мой код выглядит следующим образом. У меня есть конечная точка, которая открывает соединение с БД (к БД Postgres) и выполняет некоторые запросы к БД, например:

POST '/endpoint' do
  # open a connection
  ActiveRecord::Base.establish_connection(@@db_configuration)
  # run some queries
  db_value = TableModel.find_by(xx: yy)
  return whatever
end

after do
  # after the endpoint finishes, close all open connections
  ActiveRecord::Base.clear_all_connections!
end

Когда я получаю два параллельных запроса к этой конечной точке, один из них завершается с ошибкой:

2019-01-12 00:22:07 - ActiveRecord::ConnectionNotEstablished - No connection pool with 'primary' found.:
    C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/activerecord-5.2.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:1009:in `retrieve_connection'
    C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/activerecord-5.2.2/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
    C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/activerecord-5.2.2/lib/active_record/connection_handling.rb:90:in `connection'
    C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/activerecord-5.2.2/lib/active_record/core.rb:207:in `find_by'
...

Мой процесс обнаружения пошел до сих пор.

  1. Я посмотрел на использование соединения в Postgres, думая, что я мог бы утечь соединения - нет, я, кажется, не.
  2. На всякий случай я увеличил пул соединений до 16 (соответствует 16 потокам Puma) - не помогло.
  3. Затем я посмотрел на источники ActiveRecord. Здесь я понял, почему 2) не помогло. Проблема не в том, что я не могу получить соединение , но я не могу получить пул соединений (да, да, это говорит об этом в исключении). Переменная @owner_to_pool map, из которой получается пул соединений, хранит process_id в качестве ключа, а в качестве значений - пулы соединений (фактически, это значение также является картой, где ключ - это спецификация соединения и значение, Я полагаю, это фактический экземпляр пула). В моем случае у меня есть только одна спецификация соединения с моей единственной базой данных.

    Но Puma - это многопоточный веб-сервер. Все запросы выполняются в одном и том же процессе, но в разных потоках.

    Из-за этого, я думаю, происходит следующее:

    • Первый запрос, начиная с process_id=X, thread=Y, «проверяет» пул соединений в establish_connection на основе process_id=X, - и «берет» его. Теперь его нет в @owner_to_pool.
    • Второй запрос, начинающийся с того же process_id=X, но с другим thread=Z, пытается сделать то же самое - но пул соединений для process_id=X отсутствует в owner_to_pool. Таким образом, второй запрос не получает пул соединений и завершается с этим исключением.
    • Первый запрос успешно завершен и возвращает пул соединений для process_id=X обратно, вызвав clear_all_connections.
    • Другой запрос, начинающийся после всего этого и не имеющий параллельных запросов в параллельных потоках, будет успешным, потому что он подберет пул соединений и вернет его снова без проблем.

Хотя я не уверен, что все понимаю на 100% правильно, но мне кажется, что-то подобное происходит.

Теперь мой вопрос: что мне делать со всем этим? Как заставить многопоточный веб-сервер Puma корректно работать с пулом соединений ActiveRecord ?

Заранее большое спасибо!


Этот вопрос кажется похожим, но, к сожалению, у него нет ответа, и у меня недостаточно репутации, чтобы прокомментировать и спросить автора, решили ли они его.

1 Ответ

0 голосов
/ 21 февраля 2019

Так что, по сути, я не осознавал, что establish_connection создает пул соединений . (Да, да, я сам так сказал в вопросе. Тем не менее, я не совсем понял это.)

То, что я закончил, это:

require ....

# create the connection pool with the required configuration - once; it'll belong to the process
ActiveRecord::Base.establish_connection(db_configuration)

at_exit {
  # close all connections on app exit
  ActiveRecord::Base.clear_all_connections!
}

class SomeClass < Sinatra::Base
  POST '/endpoint' do
    # run some queries - they'll automatically use a connection from the pool
    db_value = TableModel.find_by(xx: yy)
    return whatever
  end
end

...