Situação:
Uma instalação do Discourse com dois contêineres, com web e dados em hosts diferentes.
Problema
Uma restauração via linha de comando de um arquivo grande, de 22 GB, a partir de uma versão muito antiga do Discourse (ou seja, muitas migrações demoradas para executar) falha após 45 minutos, logo após a restauração do banco de dados.
Reconnecting to the database...
EXCEPTION: PQconsumeInput() could not receive data from server: Connection timed out
SSL SYSCALL error: Connection timed out
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in 'PG::Connection#exec'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in 'PG::Connection#async_exec'
…
from /var/www/discourse/app/models/backup_metadata.rb:16:in 'BackupMetadata.update_last_restore_date'
from /var/www/discourse/lib/backup_restore/database_restorer.rb:31:in 'BackupRestore::DatabaseRestorer#restore'
from /var/www/discourse/lib/backup_restore/restorer.rb:61:in 'BackupRestore::Restorer#run'
from script/discourse:242:in 'DiscourseCLI#restore'
…
Trying to rollback...
Cleaning stuff up...
Dropping functions from the discourse_functions schema...
Something went wrong while dropping functions from the discourse_functions schema
PQsocket() can't get socket descriptor
Teoria
O Discourse usa uma segunda conexão de banco de dados para a restauração em si e outra para a migração.
Quando essas etapas terminam, ele se reconecta ao banco de dados na conexão primária e executa BackupMetadata.update_last_restore_date, que falha imediatamente.
A razão para essa falha parece ser que a reconexão ao banco de dados não reconecta de fato.
Ela reutiliza o ConnectionHandler em cache. Veja aqui. E essa conexão é perdida após 45 minutos.
handler = connection_handlers[handler_key(spec)]
unless handler
handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
handler.establish_connection(spec.config)
connection_handlers[handler_key(spec)] = handler
end
ActiveRecord::Base.connection_handler = handler
Solução temporária
O Postgres tem tcp_keepalives_idle = 0, o que significa que ele usa a configuração do sistema operacional como padrão.
O SO tem net.ipv4.tcp_keepalive_time = 7200 (2 horas)
ALTER SYSTEM SET tcp_keepalives_idle = 60;
ALTER SYSTEM SET tcp_keepalives_interval = 30;
ALTER SYSTEM SET tcp_keepalives_count = 5;
Isso impede que a conexão seja fechada e resolve o problema.
Correção sugerida
Fazer com que o código de reconexão execute
ActiveRecord::Base.connection_handler.clear_all_connections! ou algo similar antes de reestabelecer a conexão.
Ou, de forma mais genérica, adicionar um parâmetro reconnect ao establish_connection que ignore o handler em cache.