Postgresのrecovery_prefetchが障害を引き起こす可能性
Postgres recovery_prefetch can cause outages (ardentperf.com)
要約
Postgresの`recovery_prefetch`設定は、同期レプリケーションと高負荷の書き込みが組み合わさると、本番環境で障害を引き起こす可能性があることが解説されています。この機能はレプリカでのI/O操作を増幅させ、レプリカの遅延を招き、プライマリサーバーでの書き込みを停止させる可能性があります。`recovery_prefetch`を無効にすることが解決策として提案されています。
全文翻訳
Postgresのrecovery_prefetchは有害であり、障害を引き起こすことが判明
現場で見て驚いたもう一つの話です。実はrecovery_prefetchが障害を引き起こす可能性があることがわかりました。
常に静かにバックグラウンドで起こっていたことですが、十分に注意深く見るまで気づきませんでした。
多くの作業の結果、これが数年前に誰かがPostgresハッカーリストに「これについてどう思う?」とメールを送り、皆が「いや、それほど悪くはないだろう」と思ったケースの一部であることが判明しました…そして今、私たちは本番環境での障害を目にしています!
証拠A: 2021年2月、機能が開発中であった頃、リリースから1年半前、Stephen Frost氏
- 既にプリフェッチしたブロックに対して不要なプリフェッチを発行することは、FPWが無効になっていても懸念事項と言えますが、プリフェッチすることのメリットはそれを上回る可能性が高く、将来的な改善として対処する方法を見つけることができるでしょう。それについては何も問題ありません。FPWが有効なケースを考えると、それほど単純ではありません。最終的に、あなた、Andres、Munroが懸念しておらず、他に誰も発言しないのであれば、コミットされることについて騒ぎ立てるつもりはありませんが、あなたが上で述べたように、皆が考慮すべき良い点だったと思います。
https://www.postgresql.org/message-id/20210214231815.GO27507@tamriel.snowman.net
証拠B: 2025年3月、現場からの明確な報告、Michael Zhilin氏 (Postgres Pro)
- ある顧客から、最新の強力なベアメタルサーバーで物理スタンバイに顕著なリプレイ遅延(数時間)が発生しているとの苦情がありました。“top”の出力によると、起動プロセスといくつかのカーネルワーカーがCPUを使用しており、主にカーネル空間でした。
recovery_prefetchを無効にすることでインシデントは解決しました。perfとstraceの出力を迅速に分析した結果です。実際のXLogプリフェッチコードは、共有バッファがディスクからではなくWAL FPIからロードされる可能性を考慮しておらず、そのような場合にfadvise呼び出しを行います。
https://www.postgresql.org/message-id/9bdb428a-fc8e-4801-8fff-1ad0bc6fa76b%40postgrespro.ru
これらのメールはどちらも正しい方向を向いていましたが、まだ完全な全体像を捉えていませんでした。
第一に、FPI/FPWは今日ではどこにでもあります。パフォーマンスの観点からは非常にコストがかかりますが、OSの再起動時のTorn Writeから保護するPostgresの唯一の方法です。その結果、今日のほとんどの本番Postgresインストール(コミュニティOSSバージョン)はこの設定を有効にしています。
第二に、同期レプリケーションがますます一般的になっています。コミュニティのオープンソースPostgresをハードウェア障害によるデータ損失から完全に保護するには、主に2つの方法しかありません。(1) DRBDのようなOSS技術や高価なプロプライエタリなストレージ&クラスタリング技術によるストレージレイヤーレプリケーション、または (2) Postgresの同期レプリケーションによるデータベースレイヤー保護です。後者はKubernetesとコモディティハードウェアでの通常のソリューションです。
第三に、これまで診断/報告されていなかったことですが、recovery_prefetchの問題はFPIだけではありません。プライマリインスタンスが大きなシーケンシャルI/Oを実行したときに、recovery_prefetchがそれらの操作を8kのランダムリードに変換するという事実もあります。読み取られるバイト数が同じでも、これはレプリカでIOPSを最大16倍に増幅させます。これにより、利用可能なIOPSが簡単に枯渇し、書き込みアクティビティ後にレプリカが大幅に遅延する可能性があります。
読み取りIOPSの増幅とPostgresの同期レプリケーションのための設計が組み合わさることで、recovery_prefetchは実際の障害を引き起こすキラーとなります。そして、手順を知っていれば、これは簡単に再現できることがわかりました。
- 同期レプリケーションをスタンバイに設定し、小規模なサーバー(例: 500 IOPS、2 CPU、8GBメモリのクラウドディスク)でPostgresを起動します。
- 約10GBのデータに対してpgbenchスケール640を初期化します。
- 実験の干渉を避けるため、pgbench_accountsのautovacを無効にします。
- 監視できるターミナルで、pgbenchを固定5 TPSで実行します。
- テーブルの5%を更新します。(チェックポイントを実行し、aid%20 = 0のpgbench_accountsを更新します。)これにより、書き込みアクティビティのバーストが発生します。pgbenchは完了するまで遅くなります。完了するまで待ちます。
- walreceiverプロセスをキルして、ネットワーク/システムハングや接続を中断する不安定性をエミュレートします。
- TPSがゼロに低下し、すべての書き込みが30分以上ハングします(pgbenchテーブルが大きい場合は数時間かかることもあります)。
この障害は、これほど簡単に一貫して再現できます。https://github.com/ardentperf-labs/walreceiver-outage-repro
ちなみに、VACUUM自体も書き込みアクティビティを生成する能力があります。障害のレシピは単に「十分な不運」です。ストリーミングレプリケーションを中断するネットワークグリッチと、書き込みアクティビティのバーストが同時に発生することです。
システム全体がハングする理由を理解するには、基本的に2つの単純なことに集約されます。(1) 同期レプリケーションと (2) Postgresスタンバイサーバーを定義するコア「ステートマシン」です。
同期レプリケーションでは、書き込みを受け入れることができないため、少なくとも1つの健全なスタンバイサーバーに依存していることを思い出してください。(クライアントへの耐久性を保証し、コミット前にすべてのトランザクションがリモートマシン上にあることを確認したいのです。)
コアスタンバイサーバーのステートマシンは src/backend/access/transam/xlogrecovery.c で定義されています。
WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr,
TimeLineID replayTLI, XLogRecPtr replayLSN,
bool nonblocking)
{
static TimestampTz last_fail_time = 0;
TimestampTz now;
bool streaming_reply_sent = false;
/*-------
* スタンバイモードはステートマシンによって実装されます:
*
* 1. アーカイブまたはpg_wal (XLOG_FROM_ARCHIVE) から読み取る、または単に
* pg_wal (XLOG_FROM_PG_WAL) から読み取る
* 2. プロモーショントリガー要求をチェックする
* 3. walreceiver (XLOG_FROM_STREAM) を介してプライマリサーバーから読み取る
* 4. タイムラインを再スキャンする
* 5. wal_retrieve_retry_interval ミリ秒スリープし、1に戻る。
*
* 現在のソースからの読み取りに失敗すると、ステートマシンは次の状態に進みます。
...
...
基本的に、これは次のようになります。
基本的に、PostgresスタンバイがWALリストアモードに入ると、キャッチアップするまでストリーミングに戻りません。
書き込みアクティビティのバーストと、すべてのレプリカでのIOPS増幅が大幅な遅延を引き起こすと、データベースがストリーミングを再開するのに数時間かかる可能性があります。
IOPS増幅は非常に明確です。プライマリのディスク統計(約5分間、約300 IOPS)を示します。
同じ期間のレプリカ/スタンバイのディスク状態(ディスク制限の500 IOPSで40分間)を示します。
これらのチャートはそれを図示していませんが、ディスクから読み取られるバイト数はほぼ同じです。
recovery_prefetchを無効にするとどうなるでしょうか?(FPWは有効のままです。)
UPDATE中の一時停止はまだありますが、UPDATE完了後の長い一時停止は見られませんでした。
読み取りIOPSのバーストはなく、スタンバイサーバーは基本的に追いついたままです。5%の更新を10分未満で適用しています。
現時点では、recovery_prefetch設定を全面的に無効にすることを検討していますが、議論の行方を見守りたいと思います。FPWがデフォルトで有効になっている場合、そのメリットはやや疑わしいと考えています。
付録: Postgresスタンバイサーバーのリプレイプロセス
Postgresスタンバイサーバーのコア復旧およびリプレイロジックに関わる様々なプロセスについて、より詳細な復習を以下に示します。
特に、WAL送信プロセスとWAL受信プロセスの間の矢印の方向性に注意してください。Postgresの同期レプリケーションでは、この接続は非常に重要です。それがなければプライマリは書き込みを受け入れることができません。また、接続が失われた場合、プライマリは書き込みをすべてブロックして、スタンバイがすぐに再接続するのを待つしかないため、非常に壊れやすいです!
これは、Postgresで2つのスタンバイとクォーラムコミット(ANY 1)を検討する理由です。これにより、2つではなく3つのレプリカを実行するコストは増加しますが、データベースの堅牢性が大幅に向上します。