Sequel::DatabaseDisconnectError 在 puma worker 中出现,即使数据库在 puma 的 before_fork-hook 中断开连接

Occurrence of Sequel::DatabaseDisconnectError at puma worker, even though the db is disconnected in puma's before_fork-hook

我有一个 hanami 1.3 应用程序,但问题应该与 hanami 无关。我想用 Sequel-gem 连接到第二个数据库。因此我在 hanami 的 config/environment.rb:

中定义了连接
# config/environment.rb
# ...
DWH = Sequel.connect(ENV['DWH'], :loggers => [Logger.new($stdout)])
#...

在生产中我有一个这样的 puma-config:

# config/puma.rb
require_relative './environment'
workers 5

threads_count = 1
threads threads_count, threads_count

daemonize true

preload_app!

rackup      DefaultRackup
port        2300
environment 'production'

before_fork do
  DWH.disconnect
end

on_worker_boot do
  Hanami.boot
end

我使用 before_fork 挂钩断开数据库 (http://sequel.jeremyevans.net/rdoc/files/doc/fork_safety_rdoc.html)。但是一段时间后我得到了这样的错误:

Sequel::DatabaseDisconnectError: PG::UnableToSend: SSL SYSCALL error: EOF detected
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:166:in `async_exec'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:166:in `block in execute_query'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/database/logging.rb:49:in `log_connection_yield'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:166:in `execute_query'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:153:in `block in execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:129:in `check_disconnect_errors'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:153:in `execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:515:in `_execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:327:in `block (2 levels) in execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:537:in `check_database_errors'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:327:in `block in execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/database/connecting.rb:301:in `block in synchronize'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/connection_pool/threaded.rb:107:in `hold'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/database/connecting.rb:301:in `synchronize'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:327:in `execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/dataset/actions.rb:1135:in `execute'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/adapters/postgres.rb:680:in `fetch_rows'
    /home/usr/vendor/bundle/ruby/2.6.0/gems/sequel-4.49.0/lib/sequel/dataset/actions.rb:155:in `each'
  /home/usr/app/lib/repositories/dwh_repository.rb:39:in `to_a'

我和 Sequel 的作者进行了交谈。看来,puma 配置和连接方法是正确的。

看来,数据库连接被另一个网络部分(即 tcp 超时、防火墙等)丢弃了。

在这种情况下,这是 Sequel 的预期行为:

The application lost connection to the database. When this happens, a DatabaseDisconnectError is raised and Sequel removes the connection from the connection pool. New connection will be created as needed up to the maximum pool size.

解决此问题的最佳方法是修复连接断开的原因(更改设置 [数据库、服务器])。一个务实的解决方案可能是将数据库和应用程序放在同一台服务器上。

如果这不可行,可以使用 Sequel 扩展,这可能是一种解决方法: https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html

我有同样的问题,要解决它只需增加配置中的数量或 threads,我使用预加载的 32 和 0 个工作人员(实际上是一个,但不是在集群模式下,如果我使用工人 1)

简而言之,你需要这个

workers 0   
threads 32, 32

自定义机架应用程序的完整 puma 配置

# set app root
Dir.chdir File.expand_path("../..", __FILE__)

# load RACK_ENV
require 'dotenv'
Dotenv.load

require 'bundler/setup'
Bundler.require

plugin          :tmp_restart
port            3000
log_requests    false
nakayoshi_fork  true
pidfile         './tmp/puma.pid'
state_path      './tmp/puma.state'

# activate_control_app('tcp://127.0.0.1:9000', no_token: true)

if ENV['RACK_ENV'] == 'production'
  stdout_redirect './log/puma.log', './log/puma_errros.log'
  environment 'production'
  workers     0
  threads     32, 32

  # restart if there is no response on /ok, check every 10 seconds
  Thread.new do
    loop do
      sleep 10

      unless `curl -si -m 5 http://localhost:3000/ok`.include?('200 OK')
        Logger.new('log/app_boot.log').error 'http://localhost:3000/ok fail, restarting'
        `touch tmp/restart.txt`
      end
    end
  end
end