TT Lab
はじめる
学ぶ 学習パス コース

PostgreSQLのレプリケーションと昇格

レプリケーションとはWALを流す仕事だ

TT Labで続きを見る

一言でいうと

PostgreSQLのレプリケーションは、WAL(先行書き込みログ)をスタンバイサーバーへ送り、そのまま再生することです。そのためスタンバイサーバーはプライマリサーバーのバイト単位のコピーになり、代わりに異なるバージョンを混在させることはできません。

なぜ必要なのか

データベースが止まればサービスも止まります。バックアップがあっても復旧には数時間かかります。レプリケーションはあらかじめ作っておいたコピーを持つことで、その時間を分単位に縮めます。

PostgreSQLがすることは単純です。すべての変更はまずWALに書かれます(そうしてこそクラッシュから復旧できます)。レプリケーションはそのWALをネットワークで流し、スタンバイサーバーが自分のデータにそのまま再生します。

ここから2つのことが導かれます。

どう動くのか

構築の順序は4つのステップです。

1) 주 서버 설정        wal_level=replica, max_wal_senders, 복제 계정
2) 복제 슬롯 생성      pg_create_physical_replication_slot('s1')
3) 베이스백업          pg_basebackup -X stream -S s1 -R
4) 대기 서버 기동      standby.signal 이 있으면 자동으로 recovery 모드

pg_basebackupの-Rが、standby.signalファイルとprimary_conninfoの設定を自動で作ってくれます。このファイル1つが「あなたはスタンバイサーバーです」という目印です。

レプリケーションスロットの役割

スロットがないと、プライマリサーバーはスタンバイサーバーがどこまで受け取ったかを知りません。そのためWALを自分の都合でリサイクルし、スタンバイサーバーが少し切断されてから戻ってくると、必要なWALがすでに消えています。するとレプリケーションが壊れ、ベースバックアップからやり直すことになります。

スロットは「このスタンバイサーバーが受け取るまでWALを消さないで」という目印です。その代わり、スタンバイサーバーがいつまでも戻ってこないとWALが無限にたまってディスクが満杯になります。そこでmax_slot_wal_keep_sizeで上限を設けます。上限を超えるとスロットを諦めてディスクを守ります。どちらを失うかをあらかじめ決める設定です。

同期か非同期か

デフォルトは非同期です。プライマリサーバーはWALを送ったあと、コミットをすぐに返します。速いのですが、プライマリサーバーが突然落ちると、まだ届いていないトランザクションが失われます。

synchronous_commit = onとsynchronous_standby_namesを設定すると同期になります。スタンバイサーバーがWALを受け取ったと応答するまで、コミットが待ちます。損失がない代わりに、コミットの遅延がネットワークの往復の分だけ増え、スタンバイサーバーが落ちるとプライマリサーバーの書き込みが止まります。

そのため同期レプリケーションは、スタンバイサーバーを2台以上置き、ANY 1 (s1, s2)のように「どちらか1台が応答すればよい」という形で使うのが定石です。1台だけで同期にすると、可用性はむしろ下がります。

遅延をどう測り、何を見て驚くのか

レプリケーション遅延は1つの数値ではありません。どこまで進んだかを4つの地点に分けて見ないと、どこで詰まっているのかがわかりません。

地点 意味 ここで詰まるなら
sent_lsn プライマリサーバーが送った地点まで ネットワーク帯域幅
write_lsn スタンバイサーバーが受け取って書き込んだ地点まで スタンバイサーバーのディスク
flush_lsn ディスクに確定した地点まで fsyncの性能
replay_lsn 実際に適用されて、参照すると見える地点まで 適用の競合
select application_name,
       pg_wal_lsn_diff(sent_lsn, replay_lsn) as 적용_잔량,
       write_lag, flush_lag, replay_lag
from pg_stat_replication;

バイトではなく時間で報告します。「8MB遅れている」という言い方は、書き込みの少ない明け方には深刻ですが、バッチが動く時間帯には大したことではありません。スタンバイサーバーでnow() - pg_last_xact_replay_timestamp()を測ると、何秒前の世界を見ているかがわかり、これがユーザーの体感する値に近いものです。ただしプライマリサーバーに書き込みがまったくないとこの値は増え続けるため、アラートには残量のバイト数と合わせて設定します。

適用は直列で進みます。プライマリサーバーが複数の接続で並列に書き込んだものを、スタンバイサーバーはWALの順序どおりに1つずつ適用します。そのため、大量の更新やインデックス作成が1回あるだけで遅延が大きく開きます。スタンバイサーバーのCPUは空いているのに遅延が増えているなら、たいていこれが原因です。

クエリが適用を妨げることがあります。スタンバイサーバーで長いクエリが動いていて、そのクエリが読む行をプライマリサーバーが削除した場合、適用がそのクエリと競合します。max_standby_streaming_delayが定めた時間まで待ってから、クエリをキャンセルします。分析クエリをスタンバイサーバーに回してERROR: canceling statement due to conflict with recoveryに出くわすのが、この場面です。hot_standby_feedback = onでスタンバイサーバーが自分のクエリをプライマリサーバーに知らせるようにすると、競合は減りますが、今度はプライマリサーバーのバキュームが遅れてテーブルが膨らみます。どちらを選んでも代償があり、無料の設定はありません。

よくある勘違い

「レプリケーションがバックアップになる」という考えは誤りです。DROP TABLEを誤って実行すると、そのコマンドがレプリケートされてスタンバイサーバーでもテーブルが消えます。レプリケーションはハードウェア障害を防ぎ、バックアップは人為的なミスを防ぎます。どちらも必要です。

「スタンバイサーバーを読み取りの負荷分散に使えば無料」という考えも誤りです。スタンバイサーバーで長いクエリが動くと、WALの再生がそのクエリと競合して遅れるか、クエリがキャンセルされます(hot_standby_feedbackで緩和できますが、今度はプライマリサーバーのバキュームが遅れます)。無料のものはありません。

実務で本当に大切なこと

遅延の見方を知っておく必要があります。

-- 주 서버에서
select client_addr, state, sent_lsn, replay_lsn,
       write_lag, flush_lag, replay_lag
  from pg_stat_replication;

stateがstreamingでなければ、接続できていません。replay_lagが増え続けるなら、スタンバイサーバーが追いついていません。ディスクが遅いか、スタンバイサーバーで長いクエリが動いています。