Situation:
Eine Discourse-Installation mit zwei Containern, wobei Web und Daten auf verschiedenen Hosts laufen.
Problem
Ein großes Kommandozeilen-Restore (22 GB) aus einer sehr alten Discourse-Version (d. h. viele langwierige Migrationsprozesse) schlägt nach 45 Minuten fehl, direkt nach dem Datenbank-Restore.
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
Theorie
Discourse verwendet für den eigentlichen Restore eine zweite Datenbankverbindung und eine weitere für die Migration.
Wenn diese abgeschlossen sind, verbindet es sich über die primäre Verbindung erneut mit der Datenbank und führt BackupMetadata.update_last_restore_date aus, was sofort fehlschlägt.
Der Grund für diesen Fehler scheint zu sein, dass die Datenbankverbindung nicht tatsächlich neu aufgebaut wird.
Stattdessen wird der zwischengespeicherte ConnectionHandler wiederverwendet. Siehe hier. Und diese Verbindung ist nach 45 Minuten weg.
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
Workaround
Postgres hat tcp_keepalives_idle = 0, was bedeutet, dass auf die Betriebssystem-Einstellung zurückgegriffen wird.
Das Betriebssystem hat net.ipv4.tcp_keepalive_time = 7200 (2 Stunden)
ALTER SYSTEM SET tcp_keepalives_idle = 60;
ALTER SYSTEM SET tcp_keepalives_interval = 30;
ALTER SYSTEM SET tcp_keepalives_count = 5;
Dies verhindert, dass die Verbindung geschlossen wird, und löst das Problem.
Vorschlag zur Behebung
Der Reconnect-Code sollte
ActiveRecord::Base.connection_handler.clear_all_connections! oder Ähnliches ausführen, bevor die Verbindung neu aufgebaut wird.
Oder allgemeiner: einen reconnect-Parameter zu establish_connection hinzufügen, der den zwischengespeicherten Handler umgeht.