Il ripristino fallisce a causa del riaggancio del database non funzionante

Situazione:

Un’installazione di Discourse con due container, con web e dati su host diversi.

Problema

Un ripristino da riga di comando di grandi dimensioni (22 GB) da una versione molto vecchia di Discourse (cioè con molte migrazioni lunghe da eseguire) fallisce dopo 45 minuti, subito dopo il ripristino del database.

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

Ipotesi

Discourse utilizza una seconda connessione al database per il ripristino effettivo e un’altra per la migrazione.
Una volta completate, ricollega al database sulla sua connessione primaria ed esegue BackupMetadata.update_last_restore_date, che fallisce immediatamente.

La causa di questo fallimento sembra essere che la ricollegazione al database non avviene effettivamente.
Riutilizza il ConnectionHandler in cache. Vedi qui. E quella connessione è andata persa dopo 45 minuti.

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

Soluzione temporanea

Postgres ha tcp_keepalives_idle = 0, il che significa che fa affidamento sull’impostazione del sistema operativo.
Il sistema operativo ha net.ipv4.tcp_keepalive_time = 7200 (2 ore)

ALTER SYSTEM SET tcp_keepalives_idle = 60; 
ALTER SYSTEM SET tcp_keepalives_interval = 30; 
ALTER SYSTEM SET tcp_keepalives_count = 5;

Mantiene la connessione aperta e risolve il problema.

Correzione suggerita

Far sì che il codice di ricollegamento esegua

ActiveRecord::Base.connection_handler.clear_all_connections! o simile prima di ristabilire la connessione.

Oppure, in modo più generico, aggiungere un parametro reconnect a establish_connection che bypassi l’handler in cache.