Ситуация:
Установка Discourse с двумя контейнерами: веб-часть и данные размещены на разных хостах.
Проблема
Командная строка для восстановления из резервной копии большого размера (22 ГБ) с очень старой версии Discourse (то есть требуется выполнение множества длительных миграций) завершается ошибкой через 45 минут, сразу после восстановления базы данных.
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
Предположение
Discourse использует второе соединение с базой данных для фактического восстановления и отдельное для миграции.
Когда они завершаются, система переподключается к базе данных через основное соединение и выполняет BackupMetadata.update_last_restore_date, что приводит к немедленному сбою.
Причина этого сбоя, по всей видимости, в том, что переподключение к базе данных фактически не происходит.
Используется кэшированный ConnectionHandler. См. здесь. И это соединение разрывается через 45 минут.
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
Обходной путь
В Postgres параметр tcp_keepalives_idle = 0, что означает использование настроек ОС.
В ОС установлено net.ipv4.tcp_keepalive_time = 7200 (2 часа)
ALTER SYSTEM SET tcp_keepalives_idle = 60;
ALTER SYSTEM SET tcp_keepalives_interval = 30;
ALTER SYSTEM SET tcp_keepalives_count = 5;
Это предотвращает закрытие соединения и решает проблему.
Предлагаемое исправление
Добавить в код переподключения вызов
ActiveRecord::Base.connection_handler.clear_all_connections! или аналогичный перед повторным установлением соединения.
Или, более универсально, добавить параметр reconnect в establish_connection, который обходит кэшированный обработчик.