PostgreSQL 17 から 18 へ pg_upgrade を回すと、整合性チェックの段で止まります。
Performing Consistency Checks
-----------------------------
Checking cluster versions ok
old cluster does not use data checksums but the new one does
Failure, exiting
終了コードは 1 で、データには何も書かれていません。同じ検索で当たる表示ゆれは次のとおりです。
old cluster uses data checksums but the new one does not(向きが逆の場合。旧クラスタで有効・新クラスタで無効のとき出ます)Failure, exiting/Consistency Checksだけが手がかりになっている場合pg_checksums: error: cluster must be shut down(直そうとした先で出る別のエラー。後述します)
修正は、旧クラスタ側で checksum を有効にすることです。 先に旧クラスタの状態を見ます。
$ su postgres -c 'pg_controldata /var/lib/postgresql/17/main' | grep "Database cluster state"
Database cluster state: shut down
$ su postgres -c 'pg_checksums --enable -D /var/lib/postgresql/17/main'
pg_checksums: syncing data directory
pg_checksums: updating control file
Checksums enabled in cluster
そのあと同じ pg_upgrade を回すと Upgrade Complete まで進みます。
状態が shut down 以外なら、pg_checksums は拒否します。 プロセスが 1 つも動いていなくてもです——後半の「cluster must be shut down を返す」で扱います。
以降のコマンドは postgres ユーザで実行します。pg_ctl と initdb は root では起動を拒否します(pg_ctl: cannot be run as root)。pg_checksums は root でも動いてしまうので、揃えておくほうが安全です。
なぜ PostgreSQL 18 で出るのか:initdb の既定が反転した
オプションを 1 つも渡さずに 17 と 18 のクラスタを作り、pg_controldata で見比べると差がそのまま出ます。
$ initdb -D /tmp/d17 -U postgres # PostgreSQL 17.10
$ initdb -D /tmp/d18 -U postgres # PostgreSQL 18.4
$ pg_controldata /tmp/d17 | grep checksum
Data page checksum version: 0
$ pg_controldata /tmp/d18 | grep checksum
Data page checksum version: 1
0 が無効、1 が有効です。17 までの initdb は既定で checksum を作らず、18 の initdb は既定で作ります。
pg_upgrade は旧クラスタのページをそのまま新クラスタへ持っていくので、両側の checksum 設定が一致していないと処理を始めません。だから止まるのは移行の途中ではなく、いちばん最初の整合性チェックです。
この既定は 18 かぎりのものではありません。PostgreSQL 19 の beta でも initdb の既定は 1 のままです。
$ postgres --version
postgres (PostgreSQL) 19beta2 (Debian 19~beta2-1.pgdg13+1)
$ pg_controldata /tmp/d19 | grep checksum
Data page checksum version: 1
一致していないことだけが条件で、向きは問わない
旧クラスタを --data-checksums 付きで、新クラスタを --no-data-checksums で作ると、対になった文言で止まります。
old cluster uses data checksums but the new one does not
Failure, exiting
pg_upgrade が見ているのは「両側が同じかどうか」だけです。片方だけ有効という状態を許さないので、揃える方向は 2 つあります。
直し方:旧クラスタ側で有効にする
$ su postgres -c 'pg_ctl -D <旧クラスタ> -m fast stop'
$ su postgres -c 'pg_checksums --enable -D <旧クラスタ>'
pg_checksums: syncing data directory
Checksum operation completed
Files scanned: 948
Blocks scanned: 2823
Files written: 780
Blocks written: 2823
pg_checksums: updating control file
Checksums enabled in cluster
pg_checksums は全ブロックを読んで checksum を書き込みます。上の数字は空のクラスタのもので、実データでは所要時間がデータ量と I/O 性能に比例します。本番のクラスタで走らせる前に、停止できる時間の見積もりを取ってください(この記事では実データでの所要時間を計測していません)。
その間、クラスタが起動しないようにしておく必要があります。 上流のドキュメントはこう書いています。
Enabling checksums in a large cluster can potentially take a long time. During this operation, the cluster or other programs that write to the data directory must not be started or else data loss may occur.
pg_checksums が最初に見る pg_control の状態チェックは、この事故を防ぎません。実装のコメントがそう書いています。
/*
* Check if cluster is running. A clean shutdown is required to avoid
* random checksum failures caused by torn pages. Note that this doesn't
* guard against someone starting the cluster concurrently.
*/
チェックが通るのは開始時点の 1 回だけで、走っている最中に誰かがサーバを起こしても止まりません。実行前に、コンテナの restart ポリシーや systemd のユニット、監視からの自動復旧を止めてください。 この記事が想定している読者——docker kill や OOM でクラスタが落ちた人——は、まさに自動再起動が有効な環境にいる可能性が高いところです。
ストリーミングレプリカがある構成では、checksum の設定はノードごとに持ちます。 上流のドキュメントは、片側だけ切り替えるとページ破損に見えると警告しています。
When using a replication setup with tools which perform direct copies of relation file blocks (for example pg_rewind), enabling or disabling checksums can lead to page corruptions in the shape of incorrect checksums if the operation is not done consistently across all nodes. When enabling or disabling checksums in a replication setup, it is thus recommended to stop all the clusters before switching them all consistently.
プライマリだけで有効化しても pg_upgrade は通ります。ノード間の食い違いは、この経路のどのチェックにも現れません。
pg_checksums は旧メジャー版のバイナリを使います。17 のクラスタなら 17 の pg_checksums です。
もう一方の揃え方は、新クラスタを無効側で作り直すことです。
$ su postgres -c 'initdb -D <新クラスタ> -U postgres --no-data-checksums'
こちらでも pg_upgrade は Upgrade Complete まで通ります(実測しました)。旧クラスタが大きくて停止時間を取れない場合は、こちらのほうが速く済みます——pg_checksums の全ブロック書き換えが要らないからです。代わりに、18 が既定で有効にしたものを無効へ戻すことになり、19 でも既定は有効のままなので、次のメジャー移行で同じ判断をやり直すことになります。その分を踏まえて、旧クラスタ側を有効にするほうを本線に置いています。
pg_checksums が cluster must be shut down を返す
pg_checksums --enable を実行すると、こう返ることがあります。
$ su postgres -c 'pg_checksums --enable -D /var/lib/postgresql/17/main'
pg_checksums: error: cluster must be shut down
サーバのプロセスが 1 つも動いていなくても、この文言が出ます。pg_checksums が見ているのはプロセスの有無ではなく、pg_control に記録されたクラスタの状態です。
$ pg_controldata /var/lib/postgresql/17/main | grep "Database cluster state"
Database cluster state: in production
in production は「稼働中」という記録であって、いま動いているという意味ではありません。 クラスタを clean shutdown すると、この欄が shut down に書き換わります。スタンバイ(リカバリ中のクラスタ)を正しく止めた場合は shut down in recovery になり、pg_checksums はこの 2 つを受理します。書き換わる前にプロセスが消えた場合——docker kill、コンテナの OOM kill、pg_ctl -m immediate stop、ホストごとの電源断——記録は in production のまま残ります。
$ su postgres -c 'pg_ctl -D /tmp/old -m immediate stop'
$ su postgres -c 'pg_controldata /tmp/old' | grep "Database cluster state"
Database cluster state: in production
$ su postgres -c 'pg_checksums --enable -D /tmp/old'
pg_checksums: error: cluster must be shut down
抜け方は、一度起動してから正しく止めることです。
$ su postgres -c 'pg_ctl -D /tmp/old -l /tmp/old.log start'
$ su postgres -c 'pg_ctl -D /tmp/old -m fast stop'
$ su postgres -c 'pg_controldata /tmp/old' | grep "Database cluster state"
Database cluster state: shut down
$ su postgres -c 'pg_checksums --enable -D /tmp/old'
pg_checksums: syncing data directory
pg_checksums: updating control file
Checksums enabled in cluster
起動しなおすと、サーバのログに記録が残ります。
LOG: database system was not properly shut down; automatic recovery in progress
LOG: redo is not required
LOG: database system is ready to accept connections
クラッシュリカバリが走り、終わったところで fast シャットダウンが shut down を書きます。そのあとは pg_checksums が通ります。(redo is not required は空のクラスタで測ったときの出力です。書き込みの途中で落ちたクラスタでは WAL の適用が走ります。)
$ pg_controldata /tmp/old | grep checksum
Data page checksum version: 1
公式イメージのタグを上げただけでは、このエラーには届かない
Docker の公式イメージで postgres:17 を postgres:18 に書き換え、同じボリュームを渡すと、出るのは checksum の話ではありません。
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" (specifically, using
major-version-specific directory names). This better reflects how
PostgreSQL itself works, and how upgrades are to be performed.
See also https://github.com/docker-library/postgres/pull/1259
...
This is usually the result of upgrading the Docker image without
upgrading the underlying database using "pg_upgrade" (which requires both
versions).
公式イメージは、タグを上げただけで pg_upgrade を走らせることはしません。止まる理由はデータディレクトリのレイアウトで、18 以降はメジャー版ごとのサブディレクトリに置く形へ変わっています。checksum の食い違いに届くのは、17 と 18 のバイナリを両方用意して実際に pg_upgrade を回した人だけです。
切り分け
old cluster uses data checksums but the new one does notのほう:向きが逆で、旧クラスタが有効・新クラスタが無効です。新クラスタを--no-data-checksumsを付けずに作り直すと揃います。pg_checksums: error: cluster must be shut downで止まる:pg_controldataのDatabase cluster stateを見てください。in productionなら、一度起動して clean shutdown してから再実行します。Error: in 18+, these Docker images are configured to ...が出ている:公式イメージのデータディレクトリのレイアウトの話で、checksum とは別です。pg_upgradeはまだ動いていません。pg_upgradeが checksum 以外の項目で止まる:整合性チェックは複数あるので、Consistency Checksの直後に出ている行を読んでください。checksum 以外の理由では、pg_upgradeがカレントディレクトリにpg_upgrade_output.dを作り、詳しいログをそこへ残すことがあります(checksum の食い違いで止まった場合は、そこまで進まずに何も残りません)。- checksum を有効にしたあとの性能が気になる:checksum はページを読み書きするたびに計算されます。この記事では性能への影響を計測していません。
検証環境
tianon/postgres-upgrade@sha256:4e0592fe...(17-to-18。PostgreSQL 17.10 と 18.4 のバイナリが同居)、ネットワーク遮断(イメージの pull のみ)- 再現:オプション無しで作った 17 と 18 のクラスタに
pg_upgradeを回すとold cluster does not use data checksums but the new one doesを出して終了コード 1 - 修正:
pg_checksums --enableを旧クラスタに 1 回走らせるだけで、同じpg_upgradeがUpgrade Completeに到達して終了コード 0
再現から修正までは errfix の検証ハーネスが機械的に確認しています。initdb の既定が 17 で 0・18 で 1・19beta2 でも 1 になること、向きを入れ替えると old cluster uses data checksums but the new one does not になること、--no-data-checksums で新クラスタを作り直しても Upgrade Complete に到達すること、-m immediate で停止したクラスタの Database cluster state が in production のまま残り pg_checksums が拒否すること、起動して -m fast で止めると shut down になって通ること、pg_ctl と initdb が root では起動を拒否し pg_checksums は root でも動くこと、checksum の食い違いで止まった時点ではカレントディレクトリに何も残らないこと、公式 postgres:18 イメージに 17 のデータディレクトリを渡すと別のメッセージになることは、いずれも同じ手順で実測しています。
実行中に起動させてはいけないこと、レプリケーション構成で全ノードを一斉に切り替えること、pg_checksums が shut down と shut down in recovery の 2 つを受理することは、PostgreSQL の pg_checksums ドキュメントと src/bin/pg_checksums/pg_checksums.c によります(引用はいずれも原文)。
検証イメージの 17.10 / 18.4 は、Docker 公式イメージの postgres:17 / postgres:18 と同じパッチ版であることを確認しました(postgres:latest は 18.4 を指します)。
実データでの pg_checksums の所要時間と、checksum を有効にしたあとの性能への影響は計測していません(本文の該当箇所にもそう書いています)。ブロック数の表示は空のクラスタのもので、規模の見積もりには使えません。実行中にクラスタを起動させた場合に何が起きるかも、実際には試していません——本文に書いたのは上流ドキュメントと実装コメントの内容です。レプリケーション構成も組んでいません。