Rails 6 PostgreSQL接続を自動再接続するにはどうすればよいですか?

概要

いくつかのワーカープロセスを備えたRails 6アプリケーションがあります。このアプリは DB として PostgreSQL を使用します。場合によっては、DB が再起動し (マイナー バージョンのアップグレードなど)、ワーカーの接続が失われます。自動的に再接続されることを期待していましたが、それは起こりません。

私はdatabase.ymlでreconnect: trueフラグを使用しようとしました。同じ話です。まだ「PG::UnableToSend: サーバーへの接続がありません」というメッセージが表示されます。このオプションは PostgresqlAdapter でも使用できません。 MySQLアダプターのオプションのみだと思います。

ワーカーは Rails Runner で実行する単純なサービス クラスです

何ができるでしょうか?答えは簡単なはずだと私は信じています。

解決策

PG自動再接続用のActiveRecordパッチを作成しました。

例外の処理は最適化できますが、PG::UnableToSend、PG::ConnectionBad、PG::Error が奇妙に混在していたので、代わりに例外名を比較します。

module PostgreSQLAdapterAutoReconnectPatch
  QUERY_EXCEPTIONS = [
    "SSL connection has been closed unexpectedly",
    "server closed the connection unexpectedly",
    "no connection to the server",
  ].freeze

  CONNECTION_EXCEPTIONS = [
    "could not connect to server",
    "the database system is starting up",
  ].freeze

  def exec_query(*args)
    super(*args)
  rescue ActiveRecord::StatementInvalid => e
    raise unless recoverable_query?(e.message)

    in_transaction = transaction_manager.current_transaction.open?
    try_reconnect
    in_transaction ? raise : retry
  end

  private

  def recoverable_query?(error_message)
    QUERY_EXCEPTIONS.any? { |e| error_message.include?(e) }
  end

  def recoverable_connection?(error_message)
    CONNECTION_EXCEPTIONS.any? { |e| error_message.include?(e) }
  end

  def try_reconnect
    sleep_times = [0.1, 0.5, 1, 2, 4, 8, 16, 32]

    begin
      reconnect!
    rescue PG::Error => e
      sleep_time = sleep_times.shift
      if sleep_time && recoverable_connection?(e.message)
        logger.warn("DB Server timed out, retrying in #{sleep_time} sec")
        sleep sleep_time
        retry
      else
        logger.error(e)
        raise
      end
    end
  end
end

require "active_record/connection_adapters/postgresql_adapter"
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterAutoReconnectPatch)

に触発された