本記事の内容サマリ
- コントロールファイルを書き換えるツール pg_control_editor を作ったよ
- これを使ってOID周回を実験してみたよ
コントロールファイルとは?
PostgreSQLのインスタンス(=データベースクラスタディレクトリ)にはコントロールファイルというものがあります。$PGDATA/global/pg_control にある 8KB のバイナリファイルです。この中に何が書いてあるかは、pg_controldata コマンドで確認できます。実行するとこんな感じです。
$ pg_controldata $PGDATA
pg_control version number: 1700
Catalog version number: 202406281
Database system identifier: 7502010880767128116
Database cluster state: shut down
pg_control last modified: 2025年07月11日 09時14分30秒
Latest checkpoint location: 0/3D000028
Latest checkpoint's REDO location: 0/3D000028
Latest checkpoint's REDO WAL file: 00000007000000000000003D
Latest checkpoint's TimeLineID: 7
Latest checkpoint's PrevTimeLineID: 7
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID: 0:957
Latest checkpoint's NextOID: 24797
Latest checkpoint's NextMultiXactId: 1
Latest checkpoint's NextMultiOffset: 0
Latest checkpoint's oldestXID: 730
Latest checkpoint's oldestXID's DB: 1
Latest checkpoint's oldestActiveXID: 0
Latest checkpoint's oldestMultiXid: 1
Latest checkpoint's oldestMulti's DB: 1
Latest checkpoint's oldestCommitTsXid:0
Latest checkpoint's newestCommitTsXid:0
Time of latest checkpoint: 2025年07月11日 09時14分30秒
Fake LSN counter for unlogged rels: 0/3E8
Minimum recovery ending location: 0/0
Min recovery ending loc's timeline: 0
Backup start location: 0/0
Backup end location: 0/0
End-of-backup record required: no
wal_level setting: logical
wal_log_hints setting: off
max_connections setting: 100
max_worker_processes setting: 8
max_wal_senders setting: 10
max_prepared_xacts setting: 0
max_locks_per_xact setting: 64
track_commit_timestamp setting: off
Maximum data alignment: 8
Database block size: 8192
Blocks per segment of large relation: 131072
WAL block size: 8192
Bytes per WAL segment: 16777216
Maximum length of identifiers: 64
Maximum columns in an index: 32
Maximum size of a TOAST chunk: 1996
Size of a large-object chunk: 2048
Date/time type storage: 64-bit integers
Float8 argument passing: by value
Data page checksum version: 0
Mock authentication nonce: d52e804132a955762cbe39aaccfd6c70b66188ca36582baaf1f25265075d6363
この出力から、NextXIDなどのインスタンス全体で共通して使うカウンターの値や、WAL block sizeなどのインスタンス作成時に固定的に決まるパラメータの値が格納されていることが分かります。
コントロールファイルを書き換えたいとき
PostgreSQLを検証するにあたり、ここに書かれている値を書き換えたいことがあります。例えば、32bit整数の OID番号が周回した場合に発生する PostgreSQLバグがあったのですが、新マイナーバージョンでそれが修正されたとアナウンスされているときに、「実際に検証しておいて」と言われたとしますと、OIDが周回する状況を作らないといけません。これは結構大変です。
OIDはテーブルやインデックスなどのデータエースオブジェクトを作るごとに1つ発番されます。また、大きな列要素を格納するTOASTや、ラージオブジェクトでも使われます。OID番号を進める手段として何を選ぶにしても、数十億個何かを作るとなると、ものすごく時間がかかります。CREATE TEMP TABLE ...
を 1回 100msで実行すると 40億回回すには 12年かかる計算です。
古いバージョンの PostgeSQL はテーブル 1行ごとに OID を発番させるモードがあって、バグ報告をした人は古いバージョンで再現手順を作ったうえで「これは今のバージョンでもあてはまるから」と言っていました。
どうやって書き換えるか?
というわけで、PostgreSQLサービスを止めてコントロールファイルを書き換えたくなります。ところが pg_control はバイナリファイルです。さらに、チェックサムが付いていてバイナリエディタでいじると動作しません。
このようなときは PostgreSQLに付属する pg_resetwal を使うのが定石です。これは壊れて起動しなくなったデータベースクラスタディレクトリを、WALをリセットして、コントロールファイルを補正することで、とにかく起動できるようにするツールです。障害時のデータ救出に使用します。pg_resetwal はオプションでコントロールファイルの様々な値を指定できるようになっていますので、OID等を進めるのにも使用できます。
しかし、pg_resetwalにも不満があります。それは、必ずWALをリセットすることです。pg_resetwalなわけだから当たり前なのですが、本来用途以外に使う者にはちょっと不便です。また、コントロールファイルを上書きしてきますので、何度も試行するためにはバックアップから戻すことを繰り返す必要があって、その点も不便です。
pg_control_editor
そこで pg_control_editor というツールを作ってみました。正確に言うと作ったというほどコードは書いていません。pg_resetwal からのコピペが90%です。一言でいえば WALをリセットしない pg_resetwal です。編集可能項目もとりあえずは pg_resetwal と同じ。入力元と出力先のデータベースクラスタディレクトリを別に指定する点は異なります。
以下に --help 出力を示します。
$ pg_control_editor --help
pg_control_editor is a tool to modify a control file.
Usage:
-D, --pgdata-in=DATADIR input data directory
-d, --pgdata-out=DATADIR output data directory
-?, --help show this help, then exit
Options to override control file values:
-c, --commit-timestamp-ids=XID,XID
set oldest and newest transactions bearing
commit timestamp (zero means no change)
-e, --epoch=XIDEPOCH set next transaction ID epoch
-l, --next-wal-file=WALFILE set minimum starting location for new WAL
-m, --multixact-ids=MXID,MXID set next and oldest multitransaction ID
-o, --next-oid=OID set next OID
-O, --multixact-offset=OFFSET set next multitransaction offset
-u, --oldest-transaction-id=XID set oldest transaction ID
-x, --next-transaction-id=XID set next transaction ID
--wal-segsize=SIZE size of WAL segments, in megabytes
OID周回をやってみる
実際に pg_control_editor を使ってみましょう。動作例は PostgreSQL 17.5 を使っています。
inidb で実験用の新しいデータベースクラスタディレクトリを作ります。書き換え出力用にコピーも作ります。
$ initdb -D ./xx17
《出力省略》
$ cp -R ./xx17 ./xx17_1
以下のように pg_control_editor を実行して、pg_controldata でコントロールファイル内容を確認すると、NextOID が 4294967280 になっています。
$ pg_control_editor -D ./xx17 -d ./xx17_1 -o 0xFFFFFFF0
$ pg_controldata ./xx17_1
pg_control version number: 1700
Catalog version number: 202406281
Database system identifier: 7525724113036896867
Database cluster state: in production
pg_control last modified: 2025年07月11日 17時30分31秒
Latest checkpoint location: 0/1536810
Latest checkpoint's REDO location: 0/1536810
Latest checkpoint's REDO WAL file: 000000010000000000000001
Latest checkpoint's TimeLineID: 1
Latest checkpoint's PrevTimeLineID: 1
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID: 0:738
Latest checkpoint's NextOID: 4294967280
《後略》
書き換えられた方のデータベースクラスタディレクトリで PostgreSQLを起動して、次に発番される OID番号を確認してみます。1行データを持つテーブルをいくつか作ります。
$ pg_ctl start -D xx17_1
$ psql -U postgres -d postgres
postgres=# CREATE TABLE t1 AS SELECT 1::int id;
SELECT 1
postgres=# CREATE TABLE t2 AS SELECT 1::int id;
SELECT 1
postgres=# CREATE TABLE t3 AS SELECT 1::int id;
SELECT 1
postgres=# CREATE TABLE t4 AS SELECT 1::int id;
SELECT 1
postgres=# CREATE TABLE t5 AS SELECT 1::int id;
SELECT 1
postgres=# CREATE TABLE t6 AS SELECT 1::int id;
SELECT 1
それぞれOIDを確認していくと、、、このあたりで周回していました。
postgres=# SELECT tableoid FROM t5;
tableoid
------------
4294967295
(1 row)
postgres=# SELECT tableoid FROM t6;
tableoid
----------
16386
(1 row)
ということで、コントロールファイルをいじって動作を確認することができました。
補足事項
pg_control_editor は pg_resetwal と同じだけコントロールファイル書き換えのオプションを持ちますが、--next-transaction-id を指定すると、起動するためには結局のところ WALのリセットや $PGDATA/pg_xact/ 以下のファイルを用意したりすることが必要になります。その場合には素直に pg_resetwal を使った方が良いです。
コントロールファイルの項目は、現在の pg_control_editor で指定できるよりも、ずっとたくさんあります。ニーズが生じるたびに追加していこうかなと思っています。
以上、本記事にお付き合いいただき、ありがとうございました。