この記事は SRA Advent Calendar 2025 12月 8日の記事です。
これまでの OrioleDB に関する記事は OrioleDBタグで検索してください。また、そもそも OrioleDB とは何ぞや、と言う方は、OrioleDB を触ってみるやこちらのスライド資料を参照ください。
OrioleDB PITR はどうやる?
データベースの PITR(Point in Time Recovery)とは、障害発生直前を含む、指定した時刻に戻すリカバリであり、それを可能にするバックアップ方式を意味します。PostgreSQL であれば、pg_basebackup コマンドで取得したオンライン物理バックアップと、データ変更差分が書かれた WALファイルのアーカイブを蓄積していくことで、PITR を実現しています。ドキュメントにも詳しい説明があります。
一方、OrioleDB のドキュメントには PITR の説明がありません。アーキテクチャの章で独自の WAL とリカバリの説明はあるのだけれど、WAL の仕組みを変えたとして、PITR はどうなるの? ということが分かりません。
先に結論を言うと、OrioleDB では PostgreSQL と同じように PITR が使えます。以下に検証してみたことを報告します。
OrioleDB の PITR を検証する
こちらの記事の手順で作った環境を使用します。
まず、どの時刻までリカバリできたか分かりやすいテーブルを用意します。
odb1=# CREATE TABLE t_pitrtest1 (id serial PRIMARY KEY, ts timestamp DEFAULT now(), dat text);
odb1=# INSERT INTO t_pitrtest1 (dat) VALUES ('start');
odb1=# SELECT * FROM t_pitrtest1;
id | ts | dat
----+----------------------------+-------
1 | 2025-11-30 20:37:57.067097 | start
(1 row)
順次データを投入する pgbenchスクリプトを用意して、pgbenchで実行できるようにします。
$ cat insert_t_pitrtest1.sql
INSERT INTO t_pitrtest1 (dat) VALUES ('dat:' || random()::text);
$ pgbench -R 1 -f insert_t_pitrtest1.sql -c 1 -T 10 -n odb1
上記の引数ですと -R 1 (実行レート 1 TPS)、-c 1 (同時接続数 1)ですので、こんな風にデータが投入されます。
odb1=# SELECT * FROM t_pitrtest1;
id | ts | dat
----+----------------------------+-------------------------
1 | 2025-11-30 20:37:57.067097 | start
2 | 2025-11-30 20:41:29.81414 | dat:0.03360275779345856
3 | 2025-11-30 20:41:30.953703 | dat:0.29123590557641754
4 | 2025-11-30 20:41:31.166052 | dat:0.693811003445401
5 | 2025-11-30 20:41:31.968601 | dat:0.10015774776845565
6 | 2025-11-30 20:41:32.192674 | dat:0.5571895583180564
7 | 2025-11-30 20:41:32.818466 | dat:0.9104035803366943
8 | 2025-11-30 20:41:34.362157 | dat:0.07976987531966118
9 | 2025-11-30 20:41:36.083216 | dat:0.5679965428816829
10 | 2025-11-30 20:41:38.682022 | dat:0.688073851012398
(10 rows)
OrioleDB の WAL
OrioleDB の WAL は、専用のディレクトリ・ファイル群ではなく、PostgreSQL の WAL と同じく、pg_wal 以下の WALファイル内の WALレコードとして出力されます。pg_waldump で先ほど INSERT を繰り返したときのWAL内容を確認すると次のようになります。
$ ls -l $PGDATA/pg_wal
total 16384
-rw-------. 1 postgres postgres 16777216 Nov 30 20:45 000000010000000000000001
drwx------. 2 postgres postgres 6 Nov 30 20:27 archive_status
drwx------. 2 postgres postgres 6 Nov 30 20:27 summaries
$ pg_waldump $PGDATA/pg_wal/000000010000000000000001 | tail -15
rmgr: Transaction len (rec/tot): 34/ 34, tx: 755, lsn: 0/01998BE8, prev 0/01998B68, desc: COMMIT 2025-11-30 20:41:29.817364 JST
rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/01998C10, prev 0/01998BE8, desc: RUNNING_XACTS nextXid 756 latestCompletedXid 755 oldestRunningXid 756
rmgr: custom129 len (rec/tot): 122/ 122, tx: 0, lsn: 0/01998C50, prev 0/01998C10, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 120/ 120, tx: 0, lsn: 0/01998CD0, prev 0/01998C50, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 122/ 122, tx: 0, lsn: 0/01998D48, prev 0/01998CD0, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 121/ 121, tx: 0, lsn: 0/01998DC8, prev 0/01998D48, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 121/ 121, tx: 0, lsn: 0/01998E48, prev 0/01998DC8, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 122/ 122, tx: 0, lsn: 0/01998EC8, prev 0/01998E48, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 121/ 121, tx: 0, lsn: 0/01998F48, prev 0/01998EC8, desc: UNKNOWN (0) rmid: 129
rmgr: custom129 len (rec/tot): 120/ 120, tx: 0, lsn: 0/01998FC8, prev 0/01998F48, desc: UNKNOWN (0) rmid: 129
rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/01999040, prev 0/01998FC8, desc: RUNNING_XACTS nextXid 756 latestCompletedXid 755 oldestRunningXid 756
rmgr: XLOG len (rec/tot): 30/ 30, tx: 0, lsn: 0/01999080, prev 0/01999040, desc: CHECKPOINT_REDO wal_level replica
rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/019990A0, prev 0/01999080, desc: RUNNING_XACTS nextXid 756 latestCompletedXid 755 oldestRunningXid 756
rmgr: XLOG len (rec/tot): 114/ 114, tx: 0, lsn: 0/019990E0, prev 0/019990A0, desc: CHECKPOINT_ONLINE redo 0/1999080; tli 1; prev tli 1; fpw true; wal_level replica; xid 0:756; oid 24576; multi 1; offset 0; oldest xid 730 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 756; online
rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/01999158, prev 0/019990E0, desc: RUNNING_XACTS nextXid 756 latestCompletedXid 755 oldestRunningXid 756
custom129 という見慣れない種類の WALがたくさん出ています。これが OrioleDB のテーブルに対応したカスタムWALレコードです。注目すべきはトランザクションのコミットの WALレコードは、「rmgr: Transaction ..中略.. desc: COMMIT 2025-11-30 20:41:29.817364 JST」となっているものしか出ていないことです。今回の pgbench は1行ずつ INSERTするので、それぞれが 1トランザクションであるはずですが、それに対応したコミットの WALレコードは見当たりません。
PostgreSQLはコミットのログにタイムスタンプが付随していて、それで PITR の時刻指定に対応しています。果たして、OrileDB のテーブルで PITR はできるのでしょうか?
時刻指定リカバリをやってみる
ひとまず、PostgreSQL と同じように PITR をセットアップしてみます。
ディレクトリを用意して、archive_mode、archive_command を設定して、WALアーカイブを有効にします。
$ mkdir 17/odata_arc
$ mkdir 17/odata_bkup
$ vi $PGDATA/postgresql.conf
archive_mode = on
archive_command = 'cp %p /var/lib/pgsql/17/odata_arc/%f'
$ pg_ctl restart
先ほどの pgbench をもっとたくさんINSERTするようにパラメータを与えて実行すると、そのうちに WALアーカイブが出力されます。
$ pgbench -R 100 -f insert_t_pitrtest1.sql -c 10 -T 100 -n odb1
$ ls 17/odata_arc/
000000010000000000000001
ベースバックアップを取って、.backupファイルで完了時刻(STOP TIME)を確認します。
$ pg_basebackup -D 17/odata_bkup/bkup202511302122
$ cat 17/odata_arc/*.backup
START WAL LOCATION: 0/2000028 (file 000000010000000000000002)
STOP WAL LOCATION: 0/2000128 (file 000000010000000000000002)
CHECKPOINT LOCATION: 0/2000088
BACKUP METHOD: streamed
BACKUP FROM: primary
START TIME: 2025-11-30 21:22:58 JST
LABEL: pg_basebackup base backup
START TIMELINE: 1
STOP TIME: 2025-11-30 21:22:59 JST
STOP TIMELINE: 1
このときデータは次のようになっていました。
odb1=# SELECT * FROM t_pitrtest1 ORDER BY id DESC LIMIT 5;
id | ts | dat
-------+----------------------------+-------------------------
42419 | 2025-11-30 21:25:04.506869 | dat:0.1621811199307015
42418 | 2025-11-30 21:25:04.506737 | dat:0.09407346230024816
42417 | 2025-11-30 21:25:04.499741 | dat:0.4468218632215093
42416 | 2025-11-30 21:25:04.490913 | dat:0.9747454347627438
42415 | 2025-11-30 21:25:04.490789 | dat:0.4814442714289098
(5 rows)
11/30 の 21:23 から 21:25 までの間がリカバリ目標時刻に指定可能ということが分かります。そこで、21:24:00 時点へのリカバリを試すことにします。
やっていることは PostgreSQLドキュメントの「継続的アーカイブによるバックアップを使用した復旧」に書いてある通りのことです。
$ pg_ctl stop
$ mv $PGDATA ${PGDATA}.save
$ cp -R 17/odata_bkup/bkup202511302122 $PGDATA
$ rm -rf $PGDATA/pg_wal/*
$ cp -v ${PGDATA}.save/pg_wal/00000001???????????????? $PGDATA/pg_wal/
'/var/lib/pgsql/17/odata.save/pg_wal/000000010000000000000004' -> '/var/lib/pgsql/17/odata/pg_wal/000000010000000000000004'
'/var/lib/pgsql/17/odata.save/pg_wal/000000010000000000000005' -> '/var/lib/pgsql/17/odata/pg_wal/000000010000000000000005'
'/var/lib/pgsql/17/odata.save/pg_wal/000000010000000000000006' -> '/var/lib/pgsql/17/odata/pg_wal/000000010000000000000006'
$ touch $PGDATA/recovery.signal
$ vi $PGDATA/postgresql.conf
archive_command = 'true' #cp %p /var/lib/pgsql/17/odata_arc/%f'
restore_command = 'cp /var/lib/pgsql/17/odata_arc/%f %p'
recovery_target_time = '2025-11-30 21:24:00'
$ pg_ctl start
さて、どうでしょう。まずはログを見てみます。
$ cat $PGDATA/log/* | less
2025-11-30 21:44:44.131 JST [2455] LOG: starting OrioleDB 17.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5), 64-bit
《中略》
2025-11-30 21:44:45.350 JST [2459] LOG: starting backup recovery with redo LSN 0/2000028, checkpoint LSN 0/2000088, on timeline ID 1
2025-11-30 21:44:45.358 JST [2459] LOG: restored log file "000000010000000000000002" from archive
2025-11-30 21:44:45.370 JST [2459] LOG: starting point-in-time recovery to 2025-11-30 21:24:00+09
2025-11-30 21:44:45.388 JST [2459] LOG: orioledb recovery started.
2025-11-30 21:44:45.392 JST [2463] LOG: orioledb recovery worker 0 started.
2025-11-30 21:44:45.394 JST [2464] LOG: orioledb recovery worker 1 started.
2025-11-30 21:44:45.397 JST [2465] LOG: orioledb recovery worker 2 started.
2025-11-30 21:44:45.399 JST [2466] LOG: orioledb recovery worker 3 started.
2025-11-30 21:44:45.402 JST [2459] LOG: redo starts at 0/2000028
2025-11-30 21:44:45.410 JST [2469] LOG: orioledb recovery worker 4 started.
2025-11-30 21:44:45.411 JST [2468] LOG: orioledb recovery worker 5 started.
2025-11-30 21:44:45.425 JST [2459] LOG: restored log file "000000010000000000000003" from archive
2025-11-30 21:44:45.451 JST [2459] LOG: completed backup recovery with redo LSN 0/2000028 and end LSN 0/2000128
2025-11-30 21:44:45.451 JST [2459] LOG: consistent recovery state reached at 0/2000128
2025-11-30 21:44:45.451 JST [2455] LOG: database system is ready to accept read-only connections
2025-11-30 21:44:45.566 JST [2459] LOG: recovery stopping before commit of transaction 1850, time 2025-11-30 21:24:00.253165+09
2025-11-30 21:44:45.568 JST [2459] LOG: pausing at the end of recovery
2025-11-30 21:44:45.568 JST [2459] HINT: Execute pg_wal_replay_resume() to promote.
リカバリが行われているようです。「orioledb recovery ...」という独自のログメッセージも出ています。並列に複数の recovery worker が動作するのが OrioleDB の独自性です。また、最後は「LOG: pausing at the end of recovery」になっていて指定時刻でリカバリが止まっているようです。
続いてデータを確認します。
odb1=# SELECT * FROM t_pitrtest1 ORDER BY id DESC LIMIT 5;
id | ts | dat
-------+----------------------------+-------------------------
36038 | 2025-11-30 21:24:00.246746 | dat:0.12077053940038973
36037 | 2025-11-30 21:24:00.239431 | dat:0.40804441267439406
36036 | 2025-11-30 21:24:00.225517 | dat:0.42610736088514933
36035 | 2025-11-30 21:24:00.224812 | dat:0.7284851821205414
36034 | 2025-11-30 21:24:00.224257 | dat:0.7262674959560826
(5 rows)
odb1=# SELECT pg_wal_replay_resume();
pg_wal_replay_resume
----------------------
(1 row)
データの最後が 2025-11-30 21:24:00.246746 ですので、指定が効いています。成功していますので、PostgreSQL の作法通り pg_wal_replay_resume() でリカバリを終了させました。
PostgreSQL と同じように PITR ができることが確認できました。
Oriole のコミット時刻はどこに書いてあるの?
ところで各 INSERT のコミット時刻はどこに書いてあるのでしょうか? pg_waldump には現れないだけで、OrioleDB のカスタム WALレコードの中に書いてあるのでしょうか。
OrioleDB の recovery/recovery.c という分かりやすいソースファイルに、カスタムWALレコードから日付時刻を取り出して、リカバリ停止判定をするコードがありました。これが PostgreSQL へのパッチで付加した、OrioleDB専用のhookコードになっているのでしょう。
/*
* This function is called by the RecoveryStopsHook. It decides whether we want
* to stop applying WAL records.
*
* Returns true if we are stopping, false otherwise.
*/
bool
orioledb_recovery_stops_before_hook(XLogReaderState *record,
TransactionId *recordXid,
TimestampTz *recordXtime)
{
Pointer startPtr = XLogRecGetData(record);
Pointer ptr = startPtr;
//{中略}
ptr = wal_container_read_header(ptr, &wal_version, &wal_flags);
//{中略}
memcpy(&xactTime, ptr, sizeof(xactTime));
ptr += sizeof(xactTime);
memcpy(&xid, ptr, sizeof(xid));
*recordXid = xid;
*recordXtime = xactTime;
if (recoveryTargetInclusive)
return xactTime > recoveryTargetTime;
else
return xactTime >= recoveryTargetTime;
}
結論
見てきました通り、OrioleDB は PostgreSQL と同じように PITR が使える、ということでした。