こんにちは。サイオステクノロジーの橋本です。
今回は PostgreSQL のバックアップ管理ツールである pgBackRest を紹介します。
コミュニティページ : pgBackRest
pgBackRest は非常に多機能で細かい制御ができる反面、
設定項目が多い点と日本語ドキュメントが少ないため、導入のハードルが高いかもしれません。
今回は少し冗長ですが、機能や設定、扱い方を詳細に説明します。
最後まで読んでいただければ、インストール~バックアップ取得~リストアまでできるレベルで解説します。
〇目次〇
用語説明
できること、機能紹介
要件
今回の環境
インストール
- PostgerSQL の設定
- pgBackRestの設定
- stanza の作成とチェック
バックアップ取得
バックアップの世代管理について
リポジトリのディレクトリ構造
リストアについて
pgBackRest の欠点
〇用語説明〇
pgBackRest では 2 つの用語があります。
本記事でもその用語を用いて説明を進めていきます。
・stanza (スタンザ)
pgBackRest をインストールしたサーバが1台で複数台の Postgres サーバのバックアップ取得が可能です。
また、それぞれの Postgres サーバごとに細かくバックアップ内容を変えることが可能です。
Postgres サーバのバックアップ方法などを定義した内容を stanza といいます。
*stanza は一区切りを示す単語で日本語だと節や回が近いニュアンスらしいです。
・リポジトリ
ディレクトリ、オブジェクトストレージといったバックアップを保存する場所、
WALセグメントをアーカイブする場所を合わせてリポジトリと言います。
・アーカイブ WAL
pgbackrest の用語ではないですが、本記事ではarchive_commandで退避されたWAL ファイルを
全てアーカイブ WAL と呼んでいます。
〇できること、機能紹介〇
pgBackRest は非常に高機能で細かい制御が可能です。
反面、パラメータが多いため設定の理解に時間がかかるかもしれません。
pgBackRest でできることを主なことは以下になります。
☆マークがついているものは、後程実際に検証します。
– 取得できるバックアップ種別
・完全バックアップ ☆
・増分バックアップ ☆
・差分バックアップ ☆
– 取得できるバックアップ
・リモートバックアップ ☆
・ローカルバックアップ
・アーカイブ WAL ファイル ☆
*アーカイブされていない WAL ファイルはバックアップされません
– 機能 複数の PostgreSQL サーババックアップ ☆
・バックアップコンテンツの圧縮 ☆
バックアップコンテンツそのものを gzip 圧縮します。
・バックアップコンテンツの並列圧縮 ☆
複数コアを用いて並列的にファイル圧縮が可能です。
・バックアップの保持期限設定 (バックアップローテション) ☆
それぞれのバックアップの保持期限や何世代保持するか設定が可能です。
・バックアップ整合性検証
バックアップ最中にファイルのチェックサムの検証が可能です。
・バックアップの中断と再開
・デルタ復元☆
DB の復元時、Postgresサーバに乗り込んでDBコンテンツを手動で削除する必要はないです。
・S3、Azure、およびGCS互換のオブジェクトストアのサポート
Azure
S3
GCS
〇要件〇
細かい要件は省略しますが、以下のポイントは特に気を付ける必要があります。
pgBackRest サーバから PostgreSQL サーバに対しパスワードなしで SSH ログインできること
PostgreSQL サーバから pgBackRest サーバに対しパスワードなしで SSH ログインできること
PostgreSQL サーバにも pgBackRest がインストールされていること
SSH ログイン設定の話は、SSH の話なので今回は割愛します。
設定方法はコミュニティマニュアルを参考にしてください。
〇今回の環境〇
ここからは実際にコマンドや設定、バックアップの取得を例示します。
前提として今回は以下の環境で検証しました。
OS や kernel , Pgbackrest のバージョンは全サーバ共通です。
OS | CentOS 7.7.1908 |
kernel | kernel-3.10.0-1062.12.1.el7.x86_64 |
Pgbackrest | pgbackrest-2.36-1.rhel7.x86_64 |
PostgerSQL サーバは以下の2つバージョンのサーバを用意しました。
PostgreSQL | postgresql14-14.1-1PGDG.rhel7.x86_64 |
postgresql10-10.19-1PGDG.rhel7.x86_64 | |
PGDATA | /pgdb/data |
PostgreSQL プロセスオーナー | pg-user |
また、前提として 1秒に 1レコード、現在時刻を挿入するスクリプトを実装しています。
これを PostgreSQL のリストア時に、いつの時点にリカバリができたか判断に利用します。
〇インストール〇
PostgreSQL のリポジトリで配布されています
そのため、以下の 2 コマンドでインストールが可能です。
yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum -y install pgbackrest
– pgBackRestの設定
pgBackRest は非常に柔軟な設定が可能です。
主に 4 箇所で設定、指定が可能です。
1: デフォルト値
2: 設定ファイルの global セクション
3: 設定ファイルの各 stanza 内
4: コマンド実行時の付与するオプション
1 の設定が最も弱く、数字が大きい箇所の設定ほど優先 (上書き) されます。
・設定サンプル (/etc/pgbackrest.conf)
[server1] pg1-host = server1 # ←サーバを指定 pg1-path = /pgdb/data # ← $PGDATAを指定 pg1-host-user = pg-user # ←ユーザを指定 repo1-retention-full = 2 # ←保持する完全バックアップの数を指定 start-fast = y # ←チェックポイントを高速に実行する compress-level = 9 # ←ファイルの圧縮レベルを指定 log-level-file = detail # ←ファイルに出力するログレベルを指定 [server2] pg1-host = server2 pg1-host-user = pg-user pg1-path = /pgdb/data [global] repo1-path = /var/lib/pgbackrest # ←バックアップとアーカイブが保存されるパス。 log-level-file = warn
上記は設定例をサンプルに説明します。
server1 と server2 の 2 つのサーバのバックアップが設定されています。
pgbackrest の言葉で言うと stanza が 2 つあるわけです。
オプション log-level-file に着目して設定の優先度について説明します。
log-level-file はファイルに出力するログレベルを指定します。
デフォルト値は info です。
つまり設定ファイルに log-level-file がない場合は info レベルのログが出力されます。
上記例では global セクションに log-level-file の設定があります。
server2 の stanza では log-level-file の設定はないため、
global セクションの log-level-file 設定が引き継がれます。
対し、server1 の stanza では log-level-file は detail が指定されます。
そのため server1 では detail レベルのログが出力されるわけです。
コマンド実行時にもオプションを付与することができます。
そして、コマンド実行時に付与したオプションが最も優先されます。
究極的には設定ファイルは編集せず、
すべてコマンド実行時のオプションとして付与することでバックアップを取得することもできます。
しかし、コミュニティは基本的に設定ファイル内部で諸々設定することが推奨しています。
– PostgerSQL の設定
アーカイブされたログを保存するために以下のように設定します。
*<stanza name>は適宜変える必要があります
archive_mode = on archive_command = 'pgbackrest --stanza=<stanza name> archive-push %p'
この設定により WAL ファイルをアーカイブする際リモートリポジトリにアーカイブしてくれます。
PostgreSQL は PITR するためには WAL ファイルとアーカイブ WAL が必要になります。
この設定をすることでアーカイブ WAL はリモートサーバである pg_backrest サーバに保管してくれます。
– pgBackRest の設定
pgBackRest サーバの設定
設定ファイル : /etc/pgbackrest.conf
[server1] pg1-host = server1 pg1-path = /pgdb/data pg1-host-user = pg-user repo1-retention-full = 2 start-fast = y compress-level = 9 log-level-file = detail [server2] pg1-host = server2 pg1-host-user = pg-user pg1-path = /pgdb/data [global] repo1-path = /var/lib/pgbackrest log-level-file = warn
server1 の設定
設定ファイル : /etc/pgbackrest.conf
[server1] pg1-path=/pgdb/data [global] repo1-path=/var/lib/pgbackrest repo1-host=backup_server repo1-host-user=pg-user
server2 の設定
設定ファイル : /etc/pgbackrest.conf
[server2] pg1-path=/pgdb/data [global] repo1-path=/var/lib/pgbackrest repo1-host=backup_server repo1-host-user=pg-user
– stanza の作成
デフォルトではコンソールには何も表示されません。
$ pgbackrest --stanza=server1 stanza-create $ pgbackrest --stanza=server2 stanza-create
– 設定内容のチェック
以下のコマンドで設定に誤りがないかチェックできます。
PostgreSQL サーバおよび pgBackRest サーバで実行しましょう。
$ pgbackrest --stanza=server1 check $ pgbackrest --stanza=server2 check
このコマンドもデフォルトでは何も表示されません。
ちょっと不安なので、もっとたくさんログを表示させてみましょう
$ pgbackrest --stanza=server1 --log-level-console=info check 2021-12-07 14:56:25.861 P00 INFO: check command begin 2.36: --exec-id=2488-96ce3a49 --log-level-console=info --log-level-file=detail --pg1-host=server1 --pg1-host-user=pg-user --pg1-path=/pgdb/data --repo1-path=/var/lib/pgbackrest --stanza=server1 2021-12-07 14:56:26.625 P00 INFO: check repo1 configuration (primary) 2021-12-07 14:56:26.830 P00 INFO: check repo1 archive for WAL (primary) 2021-12-07 14:56:27.231 P00 INFO: WAL segment 000000010000000000000040 successfully archived to '/var/lib/pgbackrest/archive/server1/14-1/0000000100000000/000000010000000000000040-b05178b362a3433ca05965aef1e664b6360b380d.gz' on repo1 2021-12-07 14:56:27.334 P00 INFO: check command end: completed successfully (1474ms) $
コンソールに出力されるログレベルは warn ですが、
コマンド実行時に info レベルを指定することで詳細なログを出力させています。
successfully と出てくれたので安心感がありますね。
〇バックアップの取得〇
以下のコマンドを pgBackRest サーバで実行することでバックアップ取得が可能です。
pgbackrest –stanza=<stanza name> –type=full backup
ですが、初回なのでちょっと詳細にログを出力してみましょう。
$ pgbackrest --stanza=server1 --log-level-console=info --type=full backup 2021-12-07 15:02:39.789 P00 INFO: backup command begin 2.36: --compress-level=9 --exec-id=2536-7156e055 --log-level-console=info --log-level-file=detail --pg1-host=server1 --pg1-host-user=pg-user --pg1-path=/pgdb/data --repo1-path=/var/lib/pgbackrest --repo1-retention-diff=1 --repo1-retention-full=2 --stanza=server1 --start-fast --type=full 2021-12-07 15:02:40.656 P00 INFO: execute non-exclusive pg_start_backup(): backup begins after the requested immediate checkpoint completes 2021-12-07 15:02:41.172 P00 INFO: backup start archive = 000000010000000000000043, lsn = 0/43000028 2021-12-07 15:02:56.879 P00 INFO: execute non-exclusive pg_stop_backup() and wait for all WAL segments to archive 2021-12-07 15:02:57.081 P00 INFO: backup stop archive = 000000010000000000000043, lsn = 0/43003BF8 2021-12-07 15:02:57.086 P00 INFO: check archive for segment(s) 000000010000000000000043:000000010000000000000043 2021-12-07 15:02:57.403 P00 INFO: new backup label = 20211207-150241F 2021-12-07 15:02:57.439 P00 INFO: full backup size = 46.8MB, file total = 1259 2021-12-07 15:02:57.439 P00 INFO: backup command end: completed successfully (17651ms) 2021-12-07 15:02:57.439 P00 INFO: expire command begin 2.36: --exec-id=2536-7156e055 --log-level-console=info --log-level-file=detail --repo1-path=/var/lib/pgbackrest --repo1-retention-diff=1 --repo1-retention-full=2 --stanza=server1 2021-12-07 15:02:57.444 P00 INFO: expire command end: completed successfully (5ms) $ $ pgbackrest --stanza=server2 --log-level-console=info --type=full backup 2021-12-07 15:04:00.120 P00 INFO: backup command begin 2.36: --exec-id=2579-64b21d1d --log-level-console=info --log-level-file=warn --pg1-host=server2 --pg1-host-user=pg-user --pg1-path=/pgdb/data --repo1-path=/var/lib/pgbackrest --repo1-retention-diff=1 --repo1-retention-full=2 --stanza=server2 --type=full 2021-12-07 15:04:00.987 P00 INFO: execute non-exclusive pg_start_backup(): backup begins after the next regular checkpoint completes 2021-12-07 15:04:01.512 P00 INFO: backup start archive = 000000010000000000000009, lsn = 0/9000028 2021-12-07 15:04:06.500 P00 INFO: execute non-exclusive pg_stop_backup() and wait for all WAL segments to archive 2021-12-07 15:04:06.711 P00 INFO: backup stop archive = 000000010000000000000009, lsn = 0/9000130 2021-12-07 15:04:06.723 P00 INFO: check archive for segment(s) 000000010000000000000009:000000010000000000000009 2021-12-07 15:04:07.039 P00 INFO: new backup label = 20211207-150401F 2021-12-07 15:04:07.072 P00 INFO: full backup size = 23.3MB, file total = 952 2021-12-07 15:04:07.072 P00 INFO: backup command end: completed successfully (6953ms) 2021-12-07 15:04:07.072 P00 INFO: expire command begin 2.36: --exec-id=2579-64b21d1d --log-level-console=info --log-level-file=warn --repo1-path=/var/lib/pgbackrest --repo1-retention-diff=1 --repo1-retention-full=2 --stanza=server2 2021-12-07 15:04:07.083 P00 INFO: expire command end: completed successfully (11ms) $
server1、server2 ともに問題なくバックアップを取得できたみたいです。
取得したバックアップの内容は以下のコマンドで確認可能です
pgbackrest –stanza=<stanza name> info
$ pgbackrest --stanza=server1 info stanza: server1 status: ok cipher: none db (current) wal archive min/max (14): 000000010000000000000037/000000010000000000000043 full backup: 20211207-150241F timestamp start/stop: 2021-12-07 15:02:41 / 2021-12-07 15:02:57 wal start/stop: 000000010000000000000043 / 000000010000000000000043 database size: 46.8MB, database backup size: 46.8MB repo1: backup set size: 5.7MB, backup size: 5.7MB $ $ pgbackrest --stanza=server2 info stanza: server2 status: ok cipher: none db (current) wal archive min/max (10): 000000010000000000000004/000000010000000000000009 full backup: 20211207-150401F timestamp start/stop: 2021-12-07 15:04:01 / 2021-12-07 15:04:06 wal start/stop: 000000010000000000000009 / 000000010000000000000009 database size: 23.3MB, database backup size: 23.3MB repo1: backup set size: 2.7MB, backup size: 2.7MB $
〇バックアップの世代管理について〇
完全バックアップの取得ができたので差分バックアップを取得してみましょう
$ pgbackrest --stanza=server1 --type=diff backup $ pgbackrest --stanza=server1 --type=diff backup
連続して2 回実行しました。バックアップを確認してみましょう。
$ pgbackrest --stanza=server1 info stanza: server1 status: ok cipher: none db (current) wal archive min/max (14): 000000010000000000000049/000000010000000000000051 full backup: 20211207-151931F timestamp start/stop: 2021-12-07 15:19:31 / 2021-12-07 15:19:47 wal start/stop: 00000001000000000000004B / 00000001000000000000004B database size: 46.9MB, database backup size: 46.9MB repo1: backup set size: 5.7MB, backup size: 5.7MB diff backup: 20211207-151931F_20211207-152024D timestamp start/stop: 2021-12-07 15:20:24 / 2021-12-07 15:20:30 wal start/stop: 000000010000000000000051 / 000000010000000000000051 database size: 46.9MB, database backup size: 11MB repo1: backup set size: 5.7MB, backup size: 1.4MB backup reference list: 20211207-151931F $
しかし、どうも差分バックアップは 1回しかとられていないようです。
「/etc/pgbackrest.conf」内で「repo1-retention-diff = 1」と定義しているため、
1 世代しか保存せず、古い世代は自動で削除してくれたわけです。
なお、増分バックアップは –type=incr を指定することで取得可能です。
$ pgbackrest --stanza=server1 --type=incr backup $ $ pgbackrest --stanza=server1 info stanza: server1 status: ok cipher: none db (current) wal archive min/max (14): 000000010000000000000049/000000010000000000000051 full backup: 20211207-151931F timestamp start/stop: 2021-12-07 15:19:31 / 2021-12-07 15:19:47 wal start/stop: 00000001000000000000004B / 00000001000000000000004B database size: 46.9MB, database backup size: 46.9MB repo1: backup set size: 5.7MB, backup size: 5.7MB diff backup: 20211207-151931F_20211207-152024D timestamp start/stop: 2021-12-07 15:20:24 / 2021-12-07 15:20:30 wal start/stop: 000000010000000000000051 / 000000010000000000000051 database size: 46.9MB, database backup size: 11MB repo1: backup set size: 5.7MB, backup size: 1.4MB backup reference list: 20211207-151931F incr backup: 20211207-151931F_20211207-152522I timestamp start/stop: 2021-12-07 15:25:22 / 2021-12-07 15:25:28 wal start/stop: 000000010000000000000053 / 000000010000000000000053 database size: 46.9MB, database backup size: 11.1MB repo1: backup set size: 5.7MB, backup size: 1.4MB backup reference list: 20211207-151931F $
増分バックアップ、差分バックアップともに紐づいている完全バックアップが削除されたら、
併せて削除されます。pgbackrest 側で柔軟にやってくれるのはうれしいポイントです
〇リポジトリのディレクトリ構造〇
リポジトリの構造は以下のようになっています。
なお、通常運用する中ではバックアップされたコンテンツを直接見るケースはほとんどないと思います。
それぞれの stanza ごとにディレクトリを作ってくれるので、
それぞれのバックアップが混ざる心配はありません。
repo1-path
┣ archive
┃ ┗ <stanza name>
┃ ┗ アーカイブ WAL
┃
┗ backup
┗ <stanza name>
┗ バックアップ
実際に中を見てみると gz 形式で圧縮されていることがわかります。
$ ls -la /var/lib/pgbackrest/backup/server1/20211207-150241F/pg_data/base/1 |more 合計 2104 drwxr-x--- 2 pg-user pg-user 8192 12月 7 15:02 . drwxr-x--- 6 pg-user pg-user 54 12月 7 15:02 .. -rw-r----- 1 pg-user pg-user 80 12月 7 15:02 112.gz -rw-r----- 1 pg-user pg-user 79 12月 7 15:02 113.gz -rw-r----- 1 pg-user pg-user 12415 12月 7 15:02 1247.gz -rw-r----- 1 pg-user pg-user 141 12月 7 15:02 1247_fsm.gz -rw-r----- 1 pg-user pg-user 68 12月 7 15:02 1247_vm.gz ~以下略~ $ ls -la /var/lib/pgbackrest/archive/server1/14-1/0000000100000000/ 合計 916 drwxr-x--- 2 pg-user pg-user 4096 12月 7 15:02 . drwxr-x--- 3 pg-user pg-user 30 12月 7 12:44 .. -rw-r----- 1 pg-user pg-user 21967 12月 7 12:44 000000010000000000000037-693af810f86df48ba56ef84e8c835d55ed806a40.gz -rw-r----- 1 pg-user pg-user 21471 12月 7 12:44 000000010000000000000038-52a5a7f24ff3fd115504d451a2769ee4616d2e43.gz -rw-r----- 1 pg-user pg-user 21319 12月 7 12:44 000000010000000000000039-944421ed6cfa487ba0331d4ac997a200876afdb2.gz ~以下略~
〇リストアについて〇
– 単純な PITR の実現
pgBackRest ではアーカイブ WAL は pgBackRest サーバに退避してくれますが、
そうでない再生中の WAL は管理外です。
そのため、PostgreSQL 標準のコマンド pg_switch_wal を用いて WAL ファイルを切り替えます。
$ date ; psql -d postgres -c "select pg_switch_wal();" ;date 2021年 12月 7日 火曜日 16:02:09 JST pg_switch_wal --------------- 0/57012910 (1 行) 2021年 12月 7日 火曜日 16:02:09 JST $
少なくとも 16:02:08 まで復元ができるはずです。
疑似的に障害を発生させてみましょう。
$ date ; kill -9 $(head -1 ${PGDATA}/postmaster.pid) ;date 2021年 12月 7日 火曜日 16:03:18 JST 2021年 12月 7日 火曜日 16:03:18 JST $
リストアは以下のコマンドで行ないます。
pgbackrest –stanza=<stanza name> –delta restore
$ rm /pgdb/data/postmaster.pid $ pgbackrest --stanza=server1 --log-level-console=info --delta restore 2021-12-07 16:08:13.495 P00 INFO: restore command begin 2.36: --delta --exec-id=3825-1ab8779d --log-level-console=info --pg1-path=/pgdb/data --repo1-host=backup_server --repo1-host-user=pg-user --repo1-path=/var/lib/pgbackrest --stanza=server1 2021-12-07 16:08:13.774 P00 INFO: repo1: restore backup set 20211207-151931F_20211207-152522I, recovery will start at 2021-12-07 15:25:22 2021-12-07 16:08:13.775 P00 INFO: remove invalid files/links/paths from '/pgdb/data' 2021-12-07 16:08:15.085 P00 INFO: write updated /pgdb/data/postgresql.auto.conf 2021-12-07 16:08:15.088 P00 INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started) 2021-12-07 16:08:15.089 P00 INFO: restore size = 46.9MB, file total = 1259 2021-12-07 16:08:15.089 P00 INFO: restore command end: completed successfully (1595ms) $
ポイントとしては 2 つあります。
1: PID ファイルの削除
PID ファイルがあると pgBackRest は PostgreSQL 起動中と判断してしまいます。
そのため、PostgreSQL が異常終了している場合などは PID ファイルを削除しましょう
2: –delta オプション
このオプションを付与すると $PGDATA 配下の掃除を pgBackRest が勝手にやってくれます。
rm $PGDATA を行うリスクがないので安心感があります。
リストアができたので、PostgreSQL を起動し、DB の中を覗いてみましょう
# systemctl start postgresql-14.service # $ psql -d INFDB -c "select * from test ORDER BY id DESC FETCH FIRST 5 ROWS ONLY;" id | date | hostname --------+---------------------+------------------------------------------------- 102142 | 2021-12-07 16:02:09 | ip-172-31-5-111.ap-northeast-1.compute.internal 102141 | 2021-12-07 16:02:08 | ip-172-31-5-111.ap-northeast-1.compute.internal 102140 | 2021-12-07 16:02:07 | ip-172-31-5-111.ap-northeast-1.compute.internal 102139 | 2021-12-07 16:02:06 | ip-172-31-5-111.ap-northeast-1.compute.internal 102138 | 2021-12-07 16:02:05 | ip-172-31-5-111.ap-northeast-1.compute.internal (5 行) $
最後に取得したバックアップは 2021-12-07 15:25:28 に取得した増分バックアップです。
15:25:28 から 16:02:09 までの情報はアーカイブ WAL から復元をしてくれたわけです。
16:02:09 から 16:03:18 のデータは再生中の WAL ファイルに含まれているデータのため消失しています。
〇pgBackRest の欠点〇
再生中の WAL ファイルは退避してくれません。
例えば PostgreSQL の更新が少ない時間が続くと WAL のアーカイブはされません。
その状態でストレージ障害が発生すると PostgreSQL は数時間前までの状態にしかリストアできません。
システム要件によっては RPO(Recovery Point Objective) は XX 分以内とあるかもしれません。
この場合、PostgreSQL の更新が少ない時間帯は pg_switch_wal を定期的に実行するなどの工夫が必要になります。
いかがでしたでしょうか。
pg_backrest は細かい制御ができる反面、設定が複雑です (おまけに日本語ドキュメントが少ない!)。
ご参考になればと思いまうす。