patorashのブログ

方向性はまだない

PITRを試してみた

PITRとは、ポイント・イン・タイム・リカバリーと言って、指定した時間まで遡ってリカバリすることができます。pg_dumpだとそのときのスナップショットになるので、実際に稼働しているサービスが突然落ちた時などに、直前まで復旧することができませんが、PITRならば、直前まで復旧することができます!ただし、設定方法と復旧作業が複雑です。

本を読みながらやってみました。

基本方針

試してみたいだけなので、まずはpgbenchで作ったデータベースを対象にやってみることにしました。

  1. pg_start_backupを実行
  2. rsyncでデータベースクラスタをバックアップする
  3. pg_stop_backupを実行する
  4. データを沢山登録する
  5. 時間を空ける(ここまでリカバリさせるため)
  6. 更にデータを登録する
  7. dbを停止させる
  8. リカバリしてみる

postgresql.confを編集

まず、postgresql.confを編集しました。 archive_modeをonにすることで、WAL(Write Ahead Log)を作成させます。WALは、データ更新系のログで、DBに反映する前に作られるやつです。リカバリ時には、ベースバックアップからバックアップ時点まで戻し、そこからWALを再適用することで復旧させていきます。しかし、WALはある程度までしか保存されず、一定数になると削除されていきます。それを消される前にアーカイブするためにコマンドを指定するのが、archive_commandです。cpコマンドでWALアーカイブを保存します。本来は、データベースクラスタと異なるディスク上に保存しておくべきものですが、試したいだけなので同じディスクに置いてます。

archive_mode = on
archive_command = 'cp %p /var/lib/pgsql/archivedir/%f'

設定後、postgresqlを再起動させました。設定ファイルの再読み込みだけでもよかったかも…。

pg_ctl restart

PITRやっていく

まずデータ投入

最初にデータ投入しときましょう。pgbenchでやります。デフォルトの初期化です。pgbench_accountsに10万件登録されます。

pgbench -i benchdb

pg_start_backupを実行

次にバックアップの開始のラベルを作成します。これは結構時間がかかるときもあります。

psql -c "SELECT pg_start_backup(now()::text)"

ベースバックアップを保存する

ベースバックアップは、rsyncを使うのが確実だそうです。pg_walディレクトリ以下にWALが作成されるのですが、どうせ後でコピーするので、ベースバックアップからは外します。また、postmaster.pidがあるとpostgresqlが起動しない場合など、トラブルになるかもしれないので、これも除外します。

 rsync -av --delete --exclude=pg_wal/* --exclude=postmaster.pid $PGDATA/* /var/lib/pgsql/backup

pg_stop_backupを実行

ベースバックアップを取り終えたので、pg_stop_backupを実行します。

 psql -c "select pg_stop_backup()"

これでバックアップ体制は出来上がってるので、データを登録していっても大丈夫なはず。

データを登録する

pgbenchで作られたテーブルに、データを登録していきます。大量にデータを登録するときに便利なgenerate_seriesを使います。10万1件目から20万件登録します。

INSERT INTO pgbench_accounts (aid, bid, abalance, filler) SELECT i, 1, 0, '' FROM generate_series(100001, 200000) AS i;

これを行うことで、大量のWALが発生します。収まらなくなったWALはarchive_commandで指定したコマンドの通りにコピーされていってます。

時間を空ける

空白時間を作ります。この時間をが何時なのかを記憶しておきます。

-bash-4.2$ psql -c "select now()"
              now
-------------------------------
 2020-08-16 17:59:10.852806+00
(1 row)

戻す時間の目安を2020-08-16 18:00:00 UTCとすることにしました。2~3分待ちます。

更にデータを登録する

20万1件目から30万件登録します。これで更にWALが作られます。

INSERT INTO pgbench_accounts (aid, bid, abalance, filler) SELECT i, 1, 0, '' FROM generate_series(200001, 300000) AS i;

postgresqlを停止する

killで止めたほうが本当の障害ぽさがあるとは思いますが、普通に止めました。

pg_ctl stop

これでリカバリを実行する条件が整いました。

リカバリする

WALをコピーする

まずは、落ち着いてWALをコピーします。WALアーカイブに移動していないものを取り出します。

cp -r ${PGDATA}/pg_wal /tmp/

元のデータベースクラスタを別名で残す

次に、元のデータベースクラスタを別名で残しておきます。消してしまうと、リカバリが失敗したときに問題の調査ができなくなるので、残しましょう。

mv ${PGDATA} ${PGDATA}.temp

ベースバックアップから復元する

rsyncでベースバックアップをデータベースクラスタを復元します。

rsync -av /var/lib/pgsql/backup/* ${PGDATA}

これでベースバックアップ時点までは戻せますが、まだWALがないので後で登録されたものが存在しない状態になります。

WALを戻す

さきほどtmpにコピーしたWALを戻します。

cp /tmp/pg_wal/* ${PGDATA}/pg_wal/

recovery.confを作成

recovery.confを作っていきます。最小限の設定は、restore_commandでWALアーカイブを取ってくる命令を書いておくことです。

vi ${PGDATA}/recovery.conf

内容は以下の通り。まずは、DBを停止させる直前まで全部戻すほうを確認します。

restore_command = 'cp /var/lib/pgsql/archivedir/%f %p'

PostgreSQLを起動

これで、後はPostgreSQLを起動すると、recovery.confがあるため、リカバリーモードで起動し、WALをWALアーカイブから順々に適用していくようになります。

pg_ctl start

これだと、全てのWALが適用されるため、pgbench_accountsには、30万件のデータが復元されます。確認してみます。

benchdb=# select aid from pgbench_accounts order by aid desc limit 5;
  aid
--------
 300000
 299999
 299998
 299997
 299996
(5 rows)

バッチリです!

指定時刻まで復元する

ところで時間を空けておいたので、今度はそこまでの復元をやってみましょう。postgresqlを停止して、データベースクラスタを削除してまた復元作業をやります。一部はもうやってるので端折ります。

pg_ctl stop
rm -rf /var/lib/pgsql/data # もうdata.tempにバックアップしてあるので先ほど復元したものは消す
rsync -av /var/lib/pgsql/backup/* ${PGDATA} # もう一度ベースバックアップを持ってくる
cp /tmp/pg_wal/* ${PGDATA}/pg_wal/ # もう一度WALを持ってくる
vi ${PGDATA}/recovery.conf

recovery.confの内容は、以下の通り。recovery_target_timeで、適用させたいところまでの時刻を指定します。この時間までであれば、データは30万件ではなく、20万件のはずです。

restore_command = 'cp /var/lib/pgsql/archivedir/%f %p'
recovery_target_time = '2020-08-16 18:00:00 UTC'

PostgreSQLを起動します。

pg_ctl start

では、psqlで確認してみます。

benchdb=# select aid from pgbench_accounts order by aid desc limit 5;
  aid
--------
 200000
 199999
 199998
 199997
 199996
(5 rows)

20万が最後のaidになってました!完璧です!

まとめ

手順が少々複雑なので、とりあえず試しておいたほうがやっぱりいいなと思いました。 本当にPITRをやろうと思ったら、WALのディレクトリや、WALアーカイブディレクトリは外部ストレージに保存するべきだし、バックアップも多重化する必要があるので、もっと複雑になるかと思います。でも決まった時間に戻すことができるので、オペレーションミスでうっかりデータを削除してしまっても、PITRならば、戻せます。マネージドデータベースを使えない場合はやっておく価値あるでしょう。