데이터베이스 재연결 오류로 인해 복원 실패

상황:

웹과 데이터가 서로 다른 호스트에 설치된 2컨테이너 Discourse 환경입니다.

문제

매우 오래된 Discourse 버전(즉, 실행해야 할 긴 마이그레이션이 많음)에서 22GB 크기의 커맨드라인 복원을 수행하면, 데이터베이스 복원이 완료된 직후 약 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은 OS 설정으로 폴백한다는 의미입니다.
OS의 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! 또는 유사한 명령을 실행하도록 합니다.

또는, 더 일반적인 방법으로 establish_connection에 캐시된 핸들을 우회하는 reconnect 매개변수를 추가합니다.