Situación:
Una instalación de Discourse de dos contenedores, con la web y los datos en hosts diferentes.
Problema
Una restauración por línea de comandos de un archivo grande (22 GB) desde una versión muy antigua de Discourse (es decir, con muchas migraciones largas que ejecutar) falla después de 45 minutos, justo después de la restauración de la base de datos.
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
Teoría
Discourse utiliza una segunda conexión a la base de datos para la restauración real y otra para la migración.
Cuando estas terminan, se reconecta a la base de datos en su conexión principal y ejecuta BackupMetadata.update_last_restore_date, lo cual falla inmediatamente.
La razón de este fallo parece ser que la reconexión a la base de datos no se realiza realmente.
Reutiliza el ConnectionHandler en caché. Véase aquí. Y esa conexión se pierde después de 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
Solución temporal
Postgres tiene tcp_keepalives_idle = 0, lo que significa que se recurre a la configuración del sistema operativo.
El sistema operativo tiene 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;
Mantiene la conexión abierta y resuelve el problema.
Corrección sugerida
Que el código de reconexión ejecute
ActiveRecord::Base.connection_handler.clear_all_connections! o algo similar antes de restablecer la conexión.
O, de forma más genérica, añadir un parámetro reconnect a establish_connection que omita el handler en caché.