Dev Study
← 分散システムの土台
レッスン 3 / 13·約 9 分·第12期 分散システムの土台

複製には遅れがある — 書いた直後に読めないことがある

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存今ここ

前回、状態はどこかに置くしかないと確認しました。今回はその置き場を2台に増やしたときに何が始まるかを、PostgreSQL の実測で見ます。

まず結論

同じデータを2台に持たせると、単一障害点は減りますが、代わりに「2台の中身がいつ揃うか」という問題が始まります。

実測では、複製の遅れは中央値 0.10 ms しかありませんでした。それでも、コミットが返った直後にレプリカを読むと 200 回中 199 回、その行はまだありませんでした。遅れが有限でありさえすれば、直後に読めば踏みます。

? 読む前に予測

非同期の複製構成で、書き込みのコミットが返った直後にレプリカ側を読みました。その行は見えるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

複製は、単一障害点を遅れと交換している

前回、状態はどこかに置くしかないと確認しました。その置き場が1つなら、そこが落ちた時点で全体が止まります。だから同じ中身を2台目に持たせます。

ここで新しい問題が始まります。2台目の中身は、1台目の中身といつ揃うのか。 PostgreSQL 公式(§26.2.8)はこう書いています。

PostgreSQL streaming replication is asynchronous by default. If the primary server crashes then some transactions that were committed may not have been replicated to the standby server, causing data loss. The amount of data loss is proportional to the replication delay at the time of failover.

既定は非同期で、失うのは耐久性です。では同期にすれば済むかというと、同じ文書が代償を書いています。

it also necessarily increases the response time for the requesting transaction. The minimum wait time is the round-trip time between primary and standby.

待ち時間の下限が往復時間という物理で決まる、という点が効きます。第23週「届いたことを確かめる — ACKと再送、そして規定と実装のズレ」で見た「確認を待つと往復ぶん遅れる」が、そのまま複製に出てきます。

具体実物

遅れは 0.10 ms。それでも 199/200 で踏む

PostgreSQL 18.6 のコンテナ2台(primary と replica、同一ホスト、ストリーミング複製、非同期)で測りました。両方に永続接続を張り、INSERT のコミット直後に replica を SELECT する、を 200 回です。

labs/h1-pg-replication.log(2026-09-18・PostgreSQL 18.6・同一ホスト)コマンド出力
--- 200回中 read-your-writes 違反 199 回 / 直後に見えた 1 回 ---
見えるまで: 最小 0.07 ms / 中央 0.10 ms / 最大 0.51 ms

独立した別の実行でも 199/200(最小 0.04 / 中央 0.10 / 最大 0.44 ms)でした。遅れは中央値 0.10 ms しかないのに、違反はほぼ全回です。

よくある説明は「複製の遅れは数秒で、負荷が高いと伸びる」ですが、ここで起きているのはそれではありません。直後に読む操作が、その 0.10 ms の中に入っているだけです。遅れをさらに縮めても、直後に読む限り同じことが起きます。

✓ ここまでの確認

「複製の遅れは 0.1 ms しかないので、書いた直後に読んでも実用上は問題ない」という説明は妥当ですか。

抽象原理

どこまで待つかを、5段階で刻む

PostgreSQL は「同期か非同期か」を synchronous_commit の5つの値で刻みます。公式の定義はこうです。

Specifies how much WAL processing must complete before the database server returns a "success" indication to the client. Valid values are remote_apply, on (the default), remote_write, local, and off.

synchronous_commit — 成功を返す前にどこまで終えているか(公式の定義より)
off待たない
ローカルの flush を待つ
localbut not for replication
standby のファイルシステムへの書き込みを待つ
remote_writeOSごと落ちると残らない
standby の durable storage への flush を待つ
on(既定)耐久性はここで得られる
standby の適用(replay)まで待つ
remote_applystandby のクエリから見える
  • ・公式 Table 19.1 で standby query consistency に印が付くのは remote_apply だけ
  • ・公式は remote_apply について This will cause much larger commit delays than previous settings と書いている
  • ・準同期(semi-synchronous)という語は PostgreSQL 公式文書には無い

見どころは、耐久性の階段と可視性の階段が別物だという点です。on は standby の durable storage まで待つので耐久性は得られますが、適用は待ちません。書いた内容が失われないことと、レプリカから読めることは、別の保証です。

具体実物

5つの値で測ると、交換しているものが数字で出る

同じ構成で synchronous_standby_names を設定し、5つの値それぞれで 200 回ずつ測りました。

labs/h1-pg-replication.log — 設定値ごとの違反回数と COMMIT 所要時間コマンド出力
synchronous_commit=off 違反 200/200 COMMIT所要 中央 0.034 ms / 最大 0.215 ms
synchronous_commit=local 違反 200/200 COMMIT所要 中央 0.133 ms / 最大 1.133 ms
synchronous_commit=on 違反 0/200 COMMIT所要 中央 0.223 ms / 最大 0.690 ms
synchronous_commit=remote_write 違反 200/200 COMMIT所要 中央 0.166 ms / 最大 0.481 ms
synchronous_commit=remote_apply 違反 0/200 COMMIT所要 中央 0.240 ms / 最大 0.482 ms

待つ段階が深いほど COMMIT が遅い(0.034 → 0.133 → 0.166 → 0.223 → 0.240 ms)。公式が文章で書いていた「耐久性と応答時間の交換」が、そのまま数字になっています。

ハイライトした remote_write が効きます。同期の側に寄せたのに違反は 200/200 です。 公式の定義どおりで、この値は standby が written it to their file systems までしか待たず、適用は待ちません。「同期にした」という言葉だけでは、直後に読めるかは決まりません。

✓ ここまでの確認

PostgreSQL 公式の Table 19.1 で、standby query consistency の列に印が付く synchronous_commit の値はどれですか。

⇄ 2つを見比べる
具体例 A

DNS のゾーン転送

受け取る側(secondary)が自分から取りに行きます。SOA の SERIAL という版番号を1つ比べて、変わっていれば転送します。確認できない状態が EXPIRE を超えて続くと、secondary は複製を破棄します。

DNS の複製の性質表
始めるのは 受け取る側(pull)
遅れの桁 秒〜時間(REFRESH 間隔)
遅れが続くと EXPIRE で複製を破棄する
具体例 B

PostgreSQL のストリーミング複製

primary が WAL を standby へ送り続けます。standby は受け取り続けるだけで、版番号を比べて取りに行く動作はありません。遅れても standby は複製を破棄せず、受け取った WAL の適用を続けます。ただしアーカイブなしのストリーミング複製で、standby が受け取る前に primary が古い WAL を再利用すると、standby はベースバックアップから作り直しになります。

PostgreSQL の複製の性質表
始めるのは 送る側(push)
遅れの桁 ミリ秒未満(実測 中央 0.10 ms)
遅れが続くと 破棄せず適用を続ける(WAL が消えれば作り直し)

DNS の権威サーバ群と、PostgreSQL の primary/standby。どちらも「同じ中身を複数台で持つ」仕組みですが、遅れの扱い方はどこが決定的に違うでしょうか。

遅れが小さいか大きいかではなく、「遅れが長く続いたとき、複製側が何をするか」を比べてみてください。

この章のまとめ

  • 複製は単一障害点を減らすが、代わりに「2台の中身がいつ揃うか」を持ち込む。PostgreSQL のストリーミング複製は既定で非同期で、失うのは耐久性。
  • 遅れが有限でありさえすれば、直後に読めば踏む。 実測では遅れ中央値 0.10 ms で違反 199/200。遅れの小ささは対策にならない。
  • 同期にすると耐久性は得られるが、公式いわく待ち時間の下限は往復時間。synchronous_commit の5つの値は、待つ深さと COMMIT 所要時間の交換そのもの(0.034 → 0.240 ms)。
  • 耐久性と可視性は別の保証。 remote_write は同期の側なのに違反 200/200。公式が standby query consistency に印を付けているのは remote_apply だけ。
  • 準同期という語は PostgreSQL 公式文書には無い。3分類で説明されたら、どの製品の用語かを疑う。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 4正解: 0

PostgreSQL 公式は、同期複製にすると最小でどれだけ待つと書いていますか。

用語集(5語)
レプリケーション(複製)
同じデータを複数の実体に持たせること。PostgreSQL のストリーミング複製は primary が WAL を standby へ送り続ける形で、既定では非同期。
read-your-writes
自分が書いた内容を、自分が直後に読んだときに見えること。複製構成でレプリカから読むと、この性質が成り立たない場合がある。
synchronous_commit
クライアントに成功を返す前に、どこまでの WAL 処理を終えているべきかを指定する設定。remote_apply / on / remote_write / local / off の5つの値を取る。
replay_lag
pg_stat_replication の列。standby が WAL を書き・flush し・適用したことを送信側が知るまでの時間。非同期 standby では、直近のトランザクションがクエリから見えるまでの遅れの近似になる。
catchup mode
standby が primary に接続した直後、まだ同期が取れていない状態を指す公式の呼び方。遅れが初めて 0 に達すると実時間のストリーミング状態へ移る。