WAL とチェックポイントの関係を 3 分で掴む
書き込みが定期的に遅くなる。 グラフに山が等間隔で並ぶ。
このときまず疑うのはチェックポイントです。
更新は 2 回書かれる
UPDATE が返ってきた時点で、変更はデータファイルに届いていません。
届いているのは WAL (Write-Ahead Log) だけです。
順番はこうです。
- 共有バッファ上のページを書き換える。この時点ではメモリだけ
- 変更内容を WAL に追記する
- コミット時に WAL を
fsyncする。ここまで来たらもう失われない - データファイルへの反映は後回し
WAL は追記だけなので、円盤の同じ場所に順に書けます。 データファイルはページが散らばっているので、書けばランダム書き込みになります。
この非対称が WAL の理由です。 遅い書き込みを後回しにして、速い書き込みだけを同期の待ちに入れます。
後回しにしたものを、まとめて片付けるのがチェックポイントです。
チェックポイントが山を作る
チェックポイントは、共有バッファ上の未反映のページをデータファイルへ流します。 このとき I/O が跳ねます。等間隔の山の正体はこれです。
引き金は 2 つです。
| 引き金 | 設定 | 既定 |
|---|---|---|
| 時間が来た | checkpoint_timeout | 5 分 |
| WAL が溜まった | max_wal_size | 1 GB |
どちらで起きているかを先に確かめてください。 打ち手が変わります。
select checkpoints_timed, checkpoints_req, buffers_written, write_time, sync_timefrom pg_stat_checkpointer;checkpoints_req (WAL 量による強制) が checkpoints_timed (時間による定期) に対して多いなら、max_wal_size が書き込み量に対して小さすぎます。
Postgres 17 より前は pg_stat_bgwriter に同じ列があります。
間隔を延ばすと山が高くなる
checkpoint_timeout を延ばせば回数は減ります。
そのぶん 1 回で流す量が増えます。
総量は変わりません。 山が低く広くなるか、高く狭くなるかが変わるだけです。
延ばす代償は復旧時間です。 クラッシュしたとき、最後のチェックポイント以降の WAL を全部読み直します。間隔が長いほど、読み直す量が増えます。
均すための設定が checkpoint_completion_target です。
既定の 0.9 は「次のチェックポイントまでの 90% の時間をかけて、少しずつ流す」という意味です。
まとめると、つまみは 3 つです。
| したいこと | 触る場所 | 代償 |
|---|---|---|
| 山の回数を減らす | checkpoint_timeout を延ばす | 復旧時間が延びる |
| WAL 起因の強制を止める | max_wal_size を増やす | 円盤を食う |
| 山をならす | checkpoint_completion_target を 1.0 へ寄せる | ほぼ無い。既定で 0.9 |
full page writes
チェックポイント直後だけ WAL の量が跳ねることがあります。
Postgres は、チェックポイント後に最初に変更されたページを、差分ではなくページ丸ごと WAL に書きます。 書き込みの途中で電源が落ちてページが半分だけ書かれる (torn page) 事故から復旧するためです。
このため、チェックポイントの間隔を短くすると WAL の総量そのものが増えます。 「山を低くするために間隔を縮める」は、WAL 量では逆に働きます。
見る順序
pg_stat_checkpointerでcheckpoints_reqとcheckpoints_timedの比を見るreqが多いならmax_wal_sizeを増やすtimedばかりで山が高いならcheckpoint_completion_targetを確かめる- それでも高いなら、山の正体はチェックポイントではない。VACUUM を疑う
4 つ目は VACUUM と肥大化を自分で作る で確かめられます。