少し、ニッチな投稿になりますが案件先でスクリプトを検討する上で苦労したことが多く色々試行錯誤したので、もし同じバージョンで同じようなスクリプト内容を検討している方がいらっしゃいましたらと思い、共有させていただきます。
まずオンラインバックアップとは何かと申しますと、システムが稼働している状態で行うバックアップのことを指します。
「pg_rman」などのオンラインバックアップやリストアなどを簡易的に行ってくれるツールを使わないでスクリプトを作成するため、「psql」コマンドによりデータベースへのクライアント接続を行いバックアップを作成していきます。
公式ドキュメントの用語では低レベルAPIによるベースバックアップの作成ということになりますが、こちらのバックアップの手法ではtarやcpioといったファイル、ディレクトリのコピーやストレージ機器のコピー機能などでデータベースクラスターのバックアップを行う手法になります。
ほぼすべてのデータベースは、1日1回のバックアップのほかにも、直前のトランザクション処理をWALファイルに書き出して出力したりすることでデータが破損した後でも破損した直前の状態に戻すことができます。
またWALファイルを利用することで指定した日時の状態にデーターベースをもとに戻したりすることができます。例えば、今日のお昼ごろに戻したいとなったときに、2024/08/15/13:15の時点のデータベースの状態に戻したりすることが可能になります。これをPITR(ポイント・イン・タイム・リカバリ)といいます。
WALファイルの書き出しを行う流れとしては、まずメモリ上にトランザクション処理を記録します。その後指定されたバッファサイズまでメモリ上で確保し、上回った時点で/pg_walディレクトリ等にWALファイルを書き出すことで直前までのトランザクション処理をバックアップとして取得することができます。
またこのWALファイルをベースバックアップ取得の際に一緒にバックアップとして保存することによって、ずっと過去に遡って指定した日時の状態にデータベースの内容を変更することもできるようになります。
オンラインバックアップ処理はバックアップ中のデータベースの更新もバックアップを取得する必要があるため、どこまでWALファイルを保存しアーカイブできたのかを記録し、その記録をもとに読み込むWALファイルを決めるため「チェックポイント」というのを作成します。
そのためバックアップを取得する際に以下のコマンドを実行しチェックポイントを打ち込みます。
SELECT pg_backup_start(label => 'label', fast => false);
ここからが苦労したポイントになりますが、
PostgreSQL15以降のオンラインバックアップではこのpg_backup_startコマンドを実行した
接続の中で実際のバックアップ処理(データベースクラスタのコピー)を行う必要があります。
linuxのcui上でpsqlコマンドを叩けば、通常意図的に接続を切らない限り継続してPostgreSQLに接続している状態で続けてコマンドを打つことができるのですが、スクリプトではpsqlコマンドを実行するごとに接続して終了するという挙動をするため、普通に実装してしまえば公式に記載のある通りバックアップが自動的に打ち切られてしまいます。
しかしもちろん策はあります。
例えば以下のようにバックスラッシュをpsqlコマンドの末尾にくっつけることで
一つの行として実行する方法
psql -c "SELECT * FROM pg_backup_start('test', false);" \
-c "\! tar cfvz /tmp/basebackup_15a.tar.gz ${PGDATA}" \
関数にしてechoでsqlコマンドを叩きパイプでpsqlに渡す方法
function pg_backup_start() {
echo "SELECT * FROM pg_backup_start('test', false);"
・・・
}
function main() {
pg_backup_start | psql
}
ヒアドキュメントでひとまとまりに実行する方法
psql << EOF
\x
SELECT * FROM pg_backup_start('test', false);
EOF
色々とやり方はありますが、難しいのはこれらの方法で行う場合、
直前のコマンドの終了ステータスを取得できないことにあります。
どうして終了ステータスが取得できないかというと、シェル上では上記いずれの方法もひとまとまりとしてコードを記載しているので、psqlコマンド自体が失敗したか否かしか判定できないのです。
そのため例えばpg_backup_startコマンドを実行した後にエラーが発生したらログを出力してスクリプトを終了といったよくあるエラーハンドリングが難しいのです。
その対策として私は、それぞれのコマンドの前に
どこで失敗したかを判断するフラグ用の変数を作成し、/tmpディレクトリにフラグを保持する一時ファイルを書き出し、psqlコマンドがすべて終わったタイミングで終了ステータスを判定し、失敗してたらどこで失敗してたかを/tmpディレクトリ下の一時ファイルからフラグを読み込み、エラー出力をしてスクリプトを終了するという対策を取りました。
本当は変数へそのまま直接渡したかったのですが、psqlコマンド上ではスクリプト内の変数に格納するということができなかったため、泣く泣く一時ファイルに書き出す処理にしました。
また、psqlコマンドをひとまとまりにして実行すると、pg_bakcup_startコマンドを実行した後に失敗しても止まらず最後までpsql内にある処理を実行してしまうため、以下のようにオプションをpsqlコマンドに追記する必要があります。
psql ON_ERROR_STOP=1
こうすることで、エラーが発生したところできちんと止まってくれるのでフラグによるエラーハンドリングが可能になります。
その後、バックアップ処理(データベースクラスタのコピー)や、pg_backup_stopを実行した後に帰ってくる値をファイルに出力などの処理をしてバックアップのバッチ処理は終了になります。
一番苦労した点としては終了ステータスが取得できなかったりうまくスクリプト内の変数にpsqlコマンド内でアクセスできなかったりすることでした。拙い技術、記事になりますがどなたかのお役に立てればうれしく思います。
今回このような手法でPostgreSQLのオンラインバックアップ処理スクリプトを実装しましたが、何か他に良い方法や誤りなどあればコメントなどでご教授頂けますと幸いです。
【参考】