代表的な面接トピック

PostgreSQL バックエンド面接:データを失わずにロジカルレプリケーションをフェイルオーバーするには?

バックエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

PostgreSQL のプライマリが、ロジカルレプリケーションを介してデータウェアハウスおよび検索サービスに注文の変更をストリーミングしています。コンシューマーに遅延が発生している間にプライマリで障害が発生する可能性があります。計画的および非計画的なフェイルオーバーを設計し、スタンバイ上のロジカルスロットが引き継ぎ可能であることを検証する方法、重複またはスキップされた LSN 範囲を回避する方法、およびスロットの連続性が証明できない場合のリカバリ方法を説明してください。

プロンプトと適用可能な役割

PostgreSQL のプライマリが、ロジカルレプリケーションを介してデータウェアハウスおよび検索サービスに注文の変更をストリーミングしています。コンシューマーに遅延が発生している間にプライマリで障害が発生する可能性があります。計画的および非計画的なフェイルオーバーを設計し、スタンバイ上のロジカルスロットが引き継ぎ可能であることを検証する方法、重複またはスキップされた LSN 範囲を回避する方法、およびスロットの連続性が証明できない場合のリカバリ方法を説明してください。

これは、バックエンド、データベースプラットフォーム、データインフラストラクチャ、および SRE の面接に適しています。PostgreSQL サブスクライバーと Debezium などの非 PostgreSQL コンシューマーを区別してください。前者は組み込みのフェイルオーバー設定を使用できますが、後者はコネクタの状態、スロット、および照合全体にわたる独立した証拠が依然として必要です。「スタンバイが同期されている」ことは、すべてのロジカルコンシューマーがシームレスに継続できることを証明するものではありません。

面接官がテストしていること

優れた回答は、検証可能な不変条件を提示します。新しいプライマリ上の論理ストリームは後戻りしてはならないこと、コンシューマーは確認済みの位置から再開し、重複は許容されるもののサイレントな欠落は禁止されること、そしてフェイルオーバーはスロットの同期とコンシューマーの進行状況に基づいてゲート制御されることです。候補者は、書き込みを一時停止してコンシューマーをドレイン(処理完了)できる計画的フェイルオーバーと、バックアップ、フルスナップショット、および照合が唯一の安全な修復方法となる可能性がある非計画的障害を明確に区別する必要があります。単に DNS をスタンバイに向けるだけでは、スロット、WAL の保持、または非 PostgreSQL コンシューマーの問題は解決されません。

回答する前に明確にすべき質問

  • コンシューマーは何ですか? PostgreSQL サブスクライバー、Debezium、ウェアハウスローダー、およびカスタムデコーダーでは、進行状況とリカバリのインターフェースが異なります。
  • 重複は許容されますか? ターゲット側で LSN、トランザクション ID、または冪等性キーによって重複排除が行われる場合、通常 at-least-once(少なくとも 1 回)配信で許容されます。スクリプトが自己主張だけで exactly-once を保証することはできません。
  • これは計画的なフェイルオーバーですか、それともディザスターフェイルオーバーですか? 計画的な切り替えでは、書き込みを一時停止してスタンバイを待機できます。非計画的な障害の後では、最後にコミットされコンシューマーによって確認された LSN が不明になる可能性があります。
  • テーブル間のトランザクション境界は重要ですか? ダウンストリームで注文と注文明細のアトミックなビューが必要な場合は、単一の行 LSN を比較するのではなく、トランザクション境界を伝播させます。
  • どれだけの WAL を保持できますか? 遅延しているスロットは WAL のクリーンアップをブロックし、プライマリのディスク枯渇を招くリスクがあります。無効化されたスロットは回復不可能な欠落を引き起こす可能性があります。
  • コンシューマーをスナップショットから再構築できますか? 連続性を証明できない場合は、フルスナップショット、バージョン照合、および縮退運転またはメンテナンス計画を準備してください。

30秒の回答フレームワーク

「私は不変条件を明示します。新しいプライマリのスロット位置がコンシューマーの最終確認位置をカバーしており、リカバリされたソース LSN が単調増加することです。重複イベントの再再生は許容されますが、いかなる範囲もサイレントにスキップされてはなりません。計画的フェイルオーバーの場合、書き込みを一時停止またはスロットルし、フェイルオーバースロットがスタンバイに同期されていることとスタンバイがコンシューマーより先行していることを検証し、安全な LSN を記録した後にコネクタを停止し、スタンバイを昇格させてコンシューマーを再開します。

PostgreSQL 17 ではフェイルオーバースロットをスタンバイに非同期で同期できますが、切り替え時には各スロットが存在し、同期されており、非一時的であり、無効化の理由がないことを確認する必要があります。Debezium などの非 PostgreSQL コンシューマーは、独自にスロットと LSN を検証する必要があります。非計画的障害の後で連続性が不明な場合は、新しいスロットを作成して継続したふりをしてはなりません。信頼できるバックアップまたはスナップショットから再構築し、キー、トランザクション、および LSN で照合します。」

ステップごとの詳細解説

ステップ 1: スロット、LSN、およびコンシューマーの進行状況をモデル化する

ロジカルレプリケーションスロットは、ソースの順序で再生できる変更ストリームを表します。restart_lsn は保持し続けなければならない最も古い WAL 位置です。コンシューマーが確認した進行状況は通常、confirmed_flush_lsn またはコネクタ固有のオフセットによって表されます。これらは異なる目的に対応します。前者は WAL の回収を制御し、後者はコンシューマーがどこまで処理したかを示します。

独立した各コンシューマーは、独立したスロットまたは明示的なブロードキャストレイヤーを持つ必要があります。1 つのシングルコンシューマースロットを複数のコンシューマーが奪い合うと、変更を受信しなかったコンシューマーでサイレントなデータの不完全性が発生する可能性があります。アプリケーションのキューだけでなく、保持されている WAL、確認済み位置、読み取り遅延、最も古い未処理トランザクション、およびディスクの空き容量を監視してください。

ステップ 2: 計画的フェイルオーバーのための証明可能なセーフポイントを作成する

計画的フェイルオーバーでは、短時間の読み取り専用またはドレインウィンドウを使用できます。各コンシューマーの最後の安全なオフセットを記録し、スタンバイの物理リプレイが必要なスロット状態をカバーするまで待機してから、フェイルオーバースロットがスタンバイ上で使用可能であることを確認します。PostgreSQL ではスロットが同期されていることの確認が必要です。failover_ready は、スロットが同期されており、非一時的であり、無効化理由がないことを意味します。

text
freeze_or_throttle_writes()
stop_consumers_after_recording_offsets()
wait_until(standby_replay_lsn >= required_slot_positions)
assert all(required_slots on standby are synced and valid)
promote(standby)
verify_slot_positions_are_monotonic()
restart_consumers_from_last_safe_offsets()
reconcile_sampled_rows_and_transactions()

PostgreSQL サブスクライバーは、サブスクリプションまたはスロットのフェイルオーバー設定を使用できます。Debezium コネクタは、オフセットを保持し、一致するスロットが新しいプライマリに存在することを確認し、同じ LSN から継続できることを証明する必要があります。スクリプトの成功終了コードは、それらの状態チェックの代わりにはなりません。

ステップ 3: 非計画的障害と重複配信を処理する

プライマリが消失した場合、スタンバイには古い WAL 位置しか含まれていない可能性があり、コンシューマーはオフセットを永続的に記録することなくイベントを受信している可能性があります。制限された重複を許容し、ソース LSN、トランザクション ID、またはドメインの冪等性キーによってダウンストリームで重複排除します。新しいプライマリのスロットは同期された状態から取得する必要があります。連続性が不明な場合、現在の WAL 位置でスロットを作成しても破棄された LSN を回復することはできません。

旧プライマリが復帰した場合、新しいプライマリと並行して書き込みを受け入れさせてはなりません。それを隔離し、信頼できるタイムラインを確立して、物理またはロジカルレプリケーションを再構築します。いかなる再接続も、一貫したスナップショットまたは明示的なログ位置から開始し、ウィンドウ期間中のトランザクション、削除、およびテーブル間の境界を照合する必要があります。

ステップ 4: スロットの無効化と WAL の増大を抑制する

遅延したままのスロットは WAL を保持し続け、プライマリのディスクおよびトランザクション ID の安全性を脅かします。まずプライマリを保護し、次にスロットがまだ回復可能かどうかを判断します。重要度の低いコンシューマーを一時停止するか、書き込みを制限するか、明示的な予算を設けた上でのみ一時ストレージを追加します。スロットがドロップされたか、無効化されたか、または必要な LSN が欠落している場合、同じ名前のスロットを作成しても履歴を復元することはできません。

最新の一貫したバックアップまたはフルスナップショットからコンシューマーを再構築し、スナップショットの位置を記録して、その位置以降の変更を消費します。キーバージョン、トランザクション ID、削除トゥームストーン、および予想件数によって照合します。照合に合格するまでコンシューマーを縮退状態に保ちます。「コンシューマーが接続された」ことはデータの完全性を意味しません。

ステップ 5: 訓練と測定を通じてフェイルオーバーを検証する

計画的な書き込みなしの切り替え、遅延しているコンシューマー、遅延したスロット同期、突然のプライマリ喪失、重複メッセージ、誤った旧プライマリの復帰、スロットの無効化、スキーマ変更、および大規模トランザクションの訓練を実施します。プライマリとスタンバイのタイムライン、スロット状態、restart_lsnconfirmed_flush_lsn、コンシューマーオフセット、トランザクション境界、およびビジネスデータの件数を記録します。

受け入れチェックには、単調増加する LSN、重複および欠落イベントの数、最も古い未処理トランザクションの経過時間、保持された WAL、フェイルオーバーの RTO/RPO、コンシューマーのリカバリ時間、およびデータベースとダウンストリーム間のサンプリングされたバージョンの差異が含まれます。フォールトインジェクション後に発見された最小の不整合であっても、保存されたバックアップまたはログ位置から再生成可能である必要があります。これにより、実際の修復パスが証明されます。

高品質な回答例

「私はまず連続性を定義します。新しいプライマリのスロットがコンシューマーの最後の安全な位置をカバーしており、ソース LSN が単調増加することです。重複は許容されますが、サイレントな欠落は許容されません。非 PostgreSQL コンシューマーにはそれぞれ専用のスロットが割り当てられ、コネクタのオフセット、スロットの状態、およびビジネス照合が 1 つの証拠セットを形成します。

計画的フェイルオーバーの場合、書き込みを一時停止またはスロットルし、すべてのコンシューマーオフセットを記録し、スタンバイの物理リプレイがスロット状態をカバーするまで待機し、すべてのフェイルオーバースロットが同期され、非一時的で有効であることを確認します。コネクタを停止し、スタンバイを昇格させ、単調増加する位置を検証し、最後の安全なオフセットから再開します。PostgreSQL サブスクライバーは組み込みのフェイルオーバー設定を使用できます。Debezium コンシューマーは、新しいスロットと LSN を個別に検証する必要があります。

非計画的な障害の場合、制限された重複を許容し、LSN またはトランザクション ID によってターゲットを冪等にします。スロットの連続性が証明できない場合は、新しいスロットを作成して継続したふりをしません。一貫したバックアップまたはフルスナップショットから再構築し、その後の変更を消費して、キー、削除、およびトランザクション境界を照合します。訓練では、保持された WAL、フェイルオーバー RPO、重複や欠落、およびダウンストリームのバージョン差異を測定しながら、スロット同期遅延、重複、旧プライマリの復帰、および大規模トランザクションを注入します。」

よくある間違い

  • DNS または接続文字列の切り替えのみを行う → ロジカルスロットとコンシューマーオフセットは自動的に連続しません → スロット、LSN、およびコンシューマーの状態に基づいてフェイルオーバーをゲート制御してください。
  • 物理スタンバイの同期をロジカルスロットの準備完了として扱う → スロット同期は非同期であり、コンシューマーより遅れる可能性があります → 各スロットの同期、有効性、およびリプレイ状態を確認してください。
  • コンシューマー間で 1 つのスロットを共有する → 単一コンシューマー向けの配信により、他のコンシューマーがサイレントに不完全な状態になります → 独立したコンシューマーごとに 1 つのスロットを使用するか、ブロードキャストレイヤーを使用してください。
  • 新しいプライマリに新しいスロットを作成する → すでに失われた LSN 範囲は回復できません → 連続性が不明な場合はスナップショットから再構築してください。
  • confirmed_flush_lsnrestart_lsn として扱う → 一方はコンシューマーの進行状況であり、もう一方は WAL の保持を制御します → これらを個別に監視して説明してください。
  • フェイルオーバーの特性として exactly-once を約束する → クラッシュウィンドウによって重複は依然として発生します → at-least-once に加えて冪等な副作用を使用し、重複を測定してください。
  • 旧プライマリをすぐに再接続する → デュアルライターにより分岐したタイムラインと LSN が作成されます → 隔離し、正規の系統を選択して、新しいタイムライン上で再構築してください。
  • データベースの可用性のみを訓練する → スロットの無効化、長いトランザクション、および WAL の増大がデータリスクを露呈させます → コンシューマーの遅延、スロットの蓄積、および大規模トランザクションを注入してください。
  • コンシューマーが接続した時点でリカバリを宣言する → 接続の成功は、行や削除が完全であったことを証明しません → キー、トランザクション境界、およびビジネスデータの件数を照合してください。

フォローアップの質問と回答

フォローアップ 1: 計画的フェイルオーバーでは書き込みを停止する必要がありますか?

必ずしもそうではありませんが、書き込みを継続すると証明ウィンドウが拡大します。データベースとスロット同期メカニズムによってスタンバイが必要なすべての位置をカバーしていることが証明できれば、読み取り専用ウィンドウを短くすることができます。それ以外の場合は、LSN ゲートを『通常は高速である』という前提に置き換えるよりも、一時的に書き込みをスロットルする方が安全です。コミットされたトランザクションが終了するのを待って、境界を記録してください。

フォローアップ 2: 非 PostgreSQL コンシューマーは新しいスロットが連続していることをどのようにして認識しますか?

コネクタオフセットとソース LSN を保持します。切り替える前に、スタンバイのスロットがその位置以降に同期されていることを確認します。切り替え後、新しいスロットの開始位置を読み取り、単調増加性を確認します。オフセットにメッセージ時刻のみが含まれ LSN が含まれていない場合、連続性は証明されません。再構築するか、ソース位置メタデータを追加してください。

フォローアップ 3: フェイルオーバー中の大規模トランザクションはどうなりますか?

コミット済み、未コミット、および部分的にデコードされた状態を区別します。ダウンストリームでトランザクションのアトミック性が必要な場合は、BEGIN/COMMIT 境界を伝播し、リカバリ後に不完全なフラグメントを破棄します。行レベルの結果整合性で十分な場合は、中間可視性とリプレイのルールを明示します。個々の行の到達順序は完全なトランザクションを証明しません。

フォローアップ 4: WAL がディスクを満杯にしたときにスロットを削除できますか?

コンシューマーが履歴を必要としなくなり、スナップショットの再構築の準備ができていることを確認した後に限ります。スロットをドロップするとスペースは解放されますが、未読の変更は恒久的に破棄されます。まず位置、バックアップ、および照合計画を保存してください。再構築後、新しいスナップショットから現在のソース位置までの間に欠落がないことを証明してください。

公開情報ソース

関連する質問