PostgreSQL フェイルオーバーとスイッチオーバーのシナリオを理解し実行する

PostgreSQL のスイッチオーバーとフェイルオーバーをいつ使用するか、レプリカの安全性を確認する方法、HA イベント中のスプリットブレインを回避する方法を学びます。

PostgreSQL フェイルオーバーとスイッチオーバーのシナリオを理解し実行する

PostgreSQL のフェイルオーバーとスイッチオーバーの違いは、あなたがページャーを保持している立場にある場合、学術的なものではありません。スイッチオーバーは計画的です。健全なプライマリがまだ存在し、書き込みをドレインでき、書き込み可能なロールをスタンバイに移す最適なタイミングを選択できます。フェイルオーバーは、プライマリが消失したり、到達不能になったり、トラフィックを提供し続けることが安全でない場合に行うものです。

この1つの違いがすべてを変えます。スイッチオーバー中は、忍耐が主な仕事です。プロモーション前にスタンバイが追いついていることを証明します。フェイルオーバー中は、封じ込めが主な仕事です。スタンバイがプロモーションされた後、古いプライマリが書き込みを受け入れ続けることができないようにします。PostgreSQL の厄介な HA インシデントのほとんどは、これらの2つの仕事のいずれかを急いで行うことから発生します。

レプリケーションの基礎:HA の基盤

PostgreSQL の高可用性はストリーミングレプリケーションに基づいて構築されており、1台のサーバーがプライマリ(またはマスター)として機能し、1台以上のサーバーがスタンバイ(またはレプリカ)として機能します。プライマリは、スタンバイを同期状態に保つために、先行書き込みログ(WAL)レコードをスタンバイにストリーミングします。

これらのロールを効果的に管理するには、プライマリノードとレプリカノードの両方に特定の構成設定が必要です。

重要な構成設定

これらの設定は、レプリケーションの動作方法とノードが相互に識別する方法を決定します。

  • wal_level:プライマリで replica 以上(論理デコードを必要とするツールを使用する場合は理想的には logical)に設定する必要があります。
  • max_wal_senders:同時 WAL センダー接続の最大数を定義します。すべての物理スタンバイ、ベースバックアップ、および同時に接続する可能性のあるレプリケーションツールに合わせてサイズ設定します。
  • hot_standby:レプリケーション中に読み取り専用クエリを許可するには、スタンバイサーバーの postgresql.confon に設定する必要があります。
  • synchronous_commit:トランザクションがいつ確認されるかを制御します。同期レプリケーションが正しく構成されている場合にのみ、より強力な耐久性を提供します。それ自体では、スタンバイを最新にすることはありません。
  • primary_conninfo:スタンバイに設定され、現在のプライマリに接続するための接続情報(ホスト、ポート、ユーザー、パスワード)を詳述します。

ベストプラクティス: HAProxy、仮想 IP の背後にある PgBouncer、サービスディスカバリレコード、またはプラットフォームのサービス抽象化など、PostgreSQL の前に安定したエンドポイントを配置します。アプリケーションは、今日どのノードがプライマリであるかを知る必要はありません。

スイッチオーバー:計画的な移行

スイッチオーバーは、アクティブなプライマリノードが意図的に廃止され、指定されたスタンバイが昇格してその役割を引き継ぐ、制御されたグレースフルなプロセスです。この手順は通常、計画的なメンテナンス、バージョンアップグレード、またはハードウェア交換に使用されます。

制御されたスイッチオーバーの手順

スイッチオーバーの目標は、プロモーション前にすべてのインフライトトランザクションがレプリケートされるのを待つことで、データ損失ゼロを確保することです。

  1. 現在のプライマリへの書き込みを停止する: 最初のステップは、現在のプライマリで新しいトランザクションがコミットされるのを防ぐことです。これは、default_transaction_read_only = on を設定するか、クライアント接続を一時的にシャットダウンすることで実現されることがよくあります。
  2. レプリケーションのキャッチアップを待つ: 指定されたスタンバイがプライマリから残りのすべての WAL レコードを受信して適用したことを確認します。レプリケーションラグは、プライマリの pg_stat_replication を使用するか、スタンバイのリカバリステータスを調べることで確認できます。
  3. スタンバイのプロモーションを開始する: 選択したスタンバイサーバーをプライマリロールに昇格させるコマンドを実行します。特定のコマンドは、使用する管理ツールによって異なります(例:pg_ctl promote またはクラスタマネージャーコマンド)。
  4. 古いプライマリを再構成する: スタンバイが正常に昇格したら、古いプライマリを新しいプライマリに従うスタンバイとして再構成する必要があります。これには、その primary_conninfo の更新が含まれます。
  5. アプリケーションをリダイレクトする: ロードバランサーまたはコネクションプーラーを更新して、トラフィックを新しいプライマリサーバーに転送します。

実用的なスイッチオーバーのチェックリストは、通常、劇的というよりは平凡に見えます。短い書き込み一時停止をアナウンスし、書き込みを続けるバックグラウンドワーカーを停止し、アプリケーションをメンテナンスモードにするかライタープールをドレインし、レプリケーション位置を確認します。古いプライマリでは、pg_stat_replication はスタンバイが WAL を受信してフラッシュしたかどうかを示します。スタンバイでは、pg_last_wal_receive_lsn()pg_last_wal_replay_lsn() は、WAL が単に到着したのか、実際にリプレイされたのかを確認するのに役立ちます。

スタンバイが接続されているという理由だけでプロモーションしないでください。スタンバイは接続されていても、大きなトランザクションをリプレイしている場合、ディスク I/O を待っている場合、またはネットワーク一時停止後にリカバリしている場合、数秒または数分遅れる可能性があります。計画的なスイッチオーバーの場合は、プロモーション前にリプレイが追いついていることを確認します。スタンバイで読み取り専用セッションが実行されている場合は、長時間実行クエリが WAL リプレイを遅延させていないかも確認します。

プロモーション後、ロールを直接テストします。

SELECT pg_is_in_recovery();

昇格されたノードは false を返す必要があります。降格されたノードは、スタンバイとして再構築または再配線された後、true を返す必要があります。

アプリケーション側も同様の注意が必要です。スイッチオーバーの前に、クライアントがライターをどのように発見するかを把握します。DNS 名に接続する場合は、DNS TTL と、クライアントが予想よりも長くアドレスをキャッシュするかどうかを理解します。PgBouncer を介して接続する場合は、プールを一時停止、リロード、または再起動する必要があるかどうかを決定します。HAProxy を使用する場合は、ヘルスチェックが書き込み可能ステータスをテストしていることを確認し、ポート 5432 が開いているかどうかだけをテストしないようにします。PostgreSQL が実行されているスタンバイは、有効な書き込みターゲットではありません。

また、ロールバックポイントを書き留めておくことをお勧めします。プロモーション前は、通常、停止して古いプライマリで書き込みを再開し、後で再試行できます。プロモーション後は、ロールバックは単純な元に戻すではなく、新しいロール変更になります。これはプロモーションが危険であることを意味するのではなく、オペレーターが自分が線のどちら側にいるかを知っておくべきであることを意味します。

フェイルオーバー:緊急対応

フェイルオーバーは、現在のプライマリサーバーが予期せず障害を起こした場合(例:ハードウェアクラッシュ、ネットワークパーティション、ソフトウェアエラー)にトリガーされる即時対応手順であり、迅速にオンラインに戻すことができません。

フェイルオーバーは本質的にデータ損失のリスクが高くなります。これは、障害が発生する前に最後の数件のコミットされたトランザクションがスタンバイにストリーミングされる時間があったという保証がないためです。

緊急フェイルオーバーの実行

フェイルオーバー手順はスピードとリカバリを目的として設計されており、多くの場合、プロモーションを自動化するために特殊なツールを利用します。

  1. 古いプライマリの健全性を判断する: 元のプライマリが本当に利用不可であり、一時的なネットワーク問題(危険な「スプリットブレイン」シナリオを防ぐ)を経験しているだけではないことを確認します。
  2. 最適なスタンバイを選択する: レプリケーションラグが最も少ないスタンバイ(WAL ストリームで最も進んでいるスタンバイ)を選択します。
  3. スタンバイを昇格させる: 昇格コマンド(pg_ctl promote)を使用して、選択したスタンバイを直ちに昇格させます。
  4. データ損失を処理する(必要な場合): クラスタが非同期レプリケーションを使用している場合、障害が発生したプライマリで失われたデータは、アプリケーションの許容範囲に応じて、手動で調整するか、単に受け入れる必要がある場合があります。
  5. 以前のプライマリを再構成する: 元のプライマリがリカバリされたら、クリーンアップ、再初期化(多くの場合、新しいプライマリからのベースバックアップが必要)、および新しいプライマリに従うように構成する必要があります。

フェイルオーバーの難しい部分は、pg_ctl promote と入力することではありません。難しい部分は、古いプライマリが安全でないことが証明されるまで安全でないものとして扱わなければならないと判断することです。古いプライマリがまだ実行されているが、アプリケーションまたはスタンバイから切り離されている場合、スプリットブレインが発生する可能性があります。つまり、2つの書き込み可能な PostgreSQL サーバーが異なる履歴を受け入れます。それが発生すると、PostgreSQL は履歴をマージしません。手動でのデータ調整、またはバックアップからの片側の復元が必要になります。

実際のインシデントでは、古いプライマリをフェンシングするために1分余分に費やす方が、翌日、2つの注文レコードが一致しない理由を説明するよりもはるかにましです。フェンシングとは、古い VM の電源をオフにすること、ネットワークインターフェースを切断すること、ライターエンドポイントを無効にすること、または古いホストが書き込みを受信できないことを保証するクラウド/プロバイダーメカニズムを使用することを意味します。正確な方法はインフラストラクチャによって異なりますが、要件は単純です。クライアントが新しいプライマリに書き込む前に、古いプライマリはそれらのクライアントから書き込み可能であってはなりません。

フェイルオーバー後は、クリーンアップを想定してください。古いプライマリが戻ってきた場合、それを新しいプライマリに軽率にポイントしてキャッチアップすることを期待しないでください。古いタイムラインに属する WAL が含まれている可能性があります。多くの環境では、前提条件が満たされている場合は pg_rewind が最も安全なパスであり、満たされていない場合は新しいプライマリからの新しいベースバックアップが最も安全なパスです。

緊急作業中に見落とされがちな詳細の1つは、レプリケーションスロットの話です。古いプライマリがスタンバイに物理レプリケーションスロットを使用していた場合、HA ツールがそれらを管理しない限り、それらのスロットは昇格したスタンバイに魔法のように移動しません。フェイルオーバー後は、新しいプライマリに存続するスタンバイが必要とするスロットがあるかどうかを確認し、放棄されたスロットが WAL を永久に保持していないかどうかを確認します。忘れられたスロットは、目に見える障害が終わってから数時間後にディスクを満たす可能性があります。

バックアップについても同じ規律を使用します。クラスタに新しいプライマリができたら、バックアップと WAL アーカイブがそのプライマリに従っていることを確認します。サービスを復元するが、バックアップを静かに停止するフェイルオーバーは、半分のリカバリにすぎません。

安全なプロモーションのためのツール:Repmgr と Patroni

pg_ctl を使用した手動プロモーションは可能ですが、堅牢な HA 環境は、フェイルオーバーとスイッチオーバーに必要な複雑な連携を管理し、構成変更とクラスタ状態管理を自動的に処理する専用ツールに依存しています。

Repmgr(レプリケーションマネージャー)

repmgr は、ノードの登録、レプリケーションの監視、および制御されたロール変更の実行を支援する軽量ツールです。正確なコマンドはバージョンとクラスタレイアウトによって異なりますが、一般的なパターンは次のとおりです。

  • スイッチオーバー: レプリケーションの健全性を確認した後、プライマリになるべきスタンバイから計画された repmgr standby switchover を実行します。
  • フェイルオーバー: フェンシングとウィットネス/クォーラムの動作が理解されテストされている場合にのみ、repmgrd に自動フェイルオーバーを実行させます。

Patroni

Patroni は、分散コンセンサスストア(etcd、ZooKeeper、Consul など)を利用してクラスタ状態を管理し、障害検出時に新しいプライマリを自動的に選出します。Patroni は、API コールまたは Kubernetes オペレーターを介してスイッチオーバーとフェイルオーバーの両方を大部分自動化し、手動介入を大幅に削減します。

Patroni を使用した例(概念的なプロモーションコマンド):

# Patroni の REST API を介したスイッチオーバーのトリガー
curl -X POST http://patroni-api-endpoint/switchover -H "Content-Type: application/json" -d '{"target": "standby_node_name"}'

スプリットブレインに関する警告: 自動フェイルオーバー中の最大の危険は、ネットワークパーティショニングにより2つのノードが誤って自分自身がプライマリであると信じてしまう「スプリットブレイン」シナリオです。Patroni などのツールは、クォーラムメカニズムを使用してこれを軽減しますが、手動セットアップでは、プライマリが1つだけ存在することを保証するために、厳格なフェンシングメカニズム(電源制御など)が必要です。

違いのまとめ

特徴 スイッチオーバー(計画済み) フェイルオーバー(緊急)
トリガー メンテナンス、アップグレード、管理上の選択 プライマリ障害(クラッシュ、停止)
データ損失リスク ほぼゼロ(適切にタイミングを計った場合) 中程度から高い(レプリケーションモードによる)
ダウンタイムの見込み 短く、制御されたダウンタイム 即時、反応的なダウンタイム
準備 事前の調整と WAL 同期の確認が必要 即時の行動とスタンバイの健全性への依存が必要

適応可能な小さなランブック

計画的なスイッチオーバーの場合、コンパクトなランブックは次のようになります。

  1. 選択したスタンバイが健全で WAL をリプレイしていることを確認します。
  2. アプリケーションの書き込みとバックグラウンドジョブを一時停止します。
  3. レプリケーションリプレイが追いついたことを確認します。
  4. HA ツールを介してスタンバイを昇格させます。
  5. ライターエンドポイントを移動します。
  6. 新しいプライマリで pg_is_in_recovery()false であることを確認します。
  7. 古いプライマリをスタンバイとして再構築または巻き戻します。
  8. 書き込みを再開し、エラー、レプリケーション、および接続数を監視します。

フェイルオーバーの場合、順序が変わります。

  1. プライマリが障害を起こしたか、安全でないことを確認します。
  2. 古いプライマリをフェンシングします。
  3. 最も進んだスタンバイを選択します。
  4. HA ツールを介してそれを昇格させます。
  5. ライターエンドポイントを1回移動します。
  6. 新しいプライマリで書き込みが機能することを確認します。
  7. レプリカ、スロット、バックアップ、および WAL アーカイブを確認します。
  8. 巻き戻しまたは再構築を介してのみ古いプライマリを再導入します。

コマンドはツールによって異なりますが、安全性の特性は変わりません。1つの書き込み可能なプライマリ、既知のレプリケーション状態、テスト済みのクライアントルーティング、および障害が発生したノードを戻すクリーンな方法。

HA 設計を信頼する前に、非本番環境で両方のパスをリハーサルしてください。スイッチオーバードリルでは、アプリケーションがクリーンに再接続し、古いプライマリが再びスタンバイになり、監視が新しいロールに従うことを証明する必要があります。フェイルオーバードリルでは、より厳格なことを証明する必要があります。障害が発生したプライマリがフェンシングされ、プロモーション用に選択されたスタンバイが利用可能な最良の候補であり、アプリケーションライターエンドポイントが1回移動し、古いプライマリが巻き戻しまたは再構築なしでは再参加できないことです。

最も安全な PostgreSQL HA チームは、フェイルオーバーを、障害発生時に入力される英雄的なコマンドではなく、テストされた運用ワークフローとして扱います。