ロールバック計画書の復旧コマンドは一度も実行されたことがなかった
目標
WALモードのSQLiteを使う顧客のサービスで、バックアップ → 証拠 → マイグレーション → 失敗の注入 → 復元 → バックアップ時点と同じことの証明を、実際に行い、その手順をmigrate.sh・restore.sh・rehearse.shに固めます。
なぜ重要なのか
ロールバック計画書の復旧コマンドは、たいてい実行してみたことがありません。サービスが動いたままcpしたバックアップは、integrity_checkがokなのにコミットが抜けていて、強制終了のあとに残った-walは、復元したファイルの上に再び適用されます。リハーサルで確認して数字で残さなければ、ロールバックが必要な日にはじめて知ることになります。
用意するもの: /opt/lab/p1a-rehearsal/。liveapp.py(注文サービスの模擬: start・status・stop・crash)、migrations/002_add_currency.sql、migrations/003_customer_unique.sqlです。 作業ディレクトリは/root/rehearsalです(サービスのDBは/root/rehearsal/live.db)。証拠のファイルは/root/rehearsal/evidenceに置きます。想定所要時間は60分で、セッションが終わると、/root/rehearsalは消えます。
ステップ
liveapp.py startでサービスを起動し、サービスが動いたまま、live.dbの本体のファイルだけをcpでコピーして(コピー先: /root/rehearsal/copies/cp-only.db)、liveとcp-onlyの行数・integrity_checkを記録します(ファイル: /root/rehearsal/evidence/cp.tsv)。- サービスが動いたまま、
.backupでバックアップを作り(ファイル: /root/rehearsal/backups/pre-v2.db)、VACUUM INTOでもバックアップを作って(ファイル: /root/rehearsal/backups/pre-v2-vacuum.db)、file<TAB>rows<TAB>integrity<TAB>journal_modeの形で記録します(ファイル: evidence/backup.tsv)。 - 2つのバックアップのファイルのsha256と
.sha3sum --schema、行数・user_version・integrity・時刻を残します(ファイル: evidence/baseline.json)。 migrate.sh DB NNN_이름.sql(プレースホルダーは名前です)の形のスクリプトを作り(ファイル: /root/rehearsal/migrate.sh)、live.dbに002を適用します。user_versionがNNN-1のときだけ適用し、すでにNNNなら何もせずに0、それ以外なら拒否します。- live.dbに003を適用して失敗させ、終了コード・user_version・内容ハッシュの前後・エラーを残します(ファイル: evidence/failure.json)。migrate.shは、失敗したら、痕跡がないようにします。
restore.sh BACKUP TARGETのスクリプトを作ります(ファイル: /root/rehearsal/restore.sh)。バックアップが完全でなければ、対象に触れずに拒否し、残った-walがあっても、バックアップと同じ内容で復元して証明します。liveapp.py crashで、デプロイ中の強制終了を起こしたあと、live.dbをpre-v2.dbで復元し、証拠を残します(ファイル: evidence/restore.json)。rehearse.sh DB WORKDIR UP.sql BROKEN.sqlのスクリプトを作ります(ファイル: /root/rehearsal/rehearse.sh)。バックアップから復元の証明までを一度に行い、WORKDIR/backup.dbと報告書を残し(報告書: WORKDIR/report.json)、1つでも証明できなければ、0以外の値で終わります。
参考
- サービスが動いている間は、あなたのsqlite3の接続が最後の接続ではないので、-walが維持されます。サービスを止めたままlive.dbを開いて閉じると、チェックポイントが動いて、ステップ1の条件が消えます(
liveapp.py startをもう一度行っても、すでに移されたあとです)。ステップ1・2は、サービスを起動したまま行います。 - バックアップのファイルは、読むだけにします:
sqlite3 'file:backups/pre-v2.db?immutable=1' 'PRAGMA integrity_check' - ファイルのヘッダーの18番目のバイト(0から数えます)が1ならロールバックジャーナル、2ならWALです:
od -An -tu1 -j18 -N1 파일(プレースホルダーはファイル名です) - 採点ツールは、あなたが起動したサービスに頼りません。ステップ4・6・8は、新しいDBを作って、スクリプトを直接動かし、残りは、証拠のファイルを、バックアップのファイルとliveappの記録(app/events.log)から計算し直して照合します。バックアップのファイルを消したり直したりすると、前のステップが再び不合格になります。
- よくある間違い:
sqlite3 db < file.sqlでマイグレーションする、-bailなしのBEGIN/COMMIT、残った-walを置いたままcpで復元する。
動いているサービスのDBをcpすると、何を失うか
サービスを起動して、live.dbをcpでコピーし(コピー先: /root/rehearsal/copies/cp-only.db)、live・cp-onlyの行数とintegrity_checkを記録してください(ファイル: /root/rehearsal/evidence/cp.tsv)。
ls -l live.db*で、本体のファイルの隣に何があるかを、まず見てください。行数は、サービスが動いている間に、live.dbで数え、コピーは、immutableのURIで、読むだけにしてください。integrity_checkの結果と行数が、互いに違う話をしていないかを見ることが、このステップの要点です。
.backupとVACUUM INTOでオンラインバックアップ
サービスが動いたまま、.backupでバックアップを作り(ファイル: /root/rehearsal/backups/pre-v2.db)、VACUUM INTOでもバックアップを作って(ファイル: /root/rehearsal/backups/pre-v2-vacuum.db)、結果を記録してください(ファイル: /root/rehearsal/evidence/backup.tsv)。
VACUUM INTOは、対象のファイルがすでにあれば失敗します。journal_modeは、ファイルのヘッダーのバイトで確認すれば、ファイルを開かなくてもわかります。2つのバックアップの行数が、ステップ1のcpのコピーと、どう違うかを比べてみてください。
復元のあとに比べる基準値を残す
2つのバックアップのsha256・.sha3sum --schema、pre-v2.dbの行数・user_version・integrity・時刻を残してください(ファイル: /root/rehearsal/evidence/baseline.json)。
ファイルのハッシュと内容のハッシュが、それぞれ何を証明するかを考えてみてください。2つのバックアップは、ファイルのハッシュが違い、内容のハッシュは同じでなければなりません。時刻はPodの時刻で、%Y-%m-%dT%H:%M:%Sの形式です。これ以降、バックアップのファイルは、開いて使わないでください。
バージョン番号を守るmigrate.sh
migrate.shを作って(ファイル: /root/rehearsal/migrate.sh)、live.dbに002_add_currency.sqlを適用してください。
バージョン番号は、ファイル名の先頭の数字から得ます。マイグレーションのファイルには、BEGIN・COMMIT・user_versionがないので、スクリプトが包む必要があります。採点ツールは、別の番号と別のuser_versionのDBでも動かしてみます。
失敗したマイグレーションは、痕跡が残ってはいけない
live.dbに003_customer_unique.sqlを適用して失敗させ、結果を残してください(ファイル: /root/rehearsal/evidence/failure.json)。migrate.shは、途中の文が失敗しても、何も変えてはいけません。
失敗の前後で、user_versionと.sha3sum --schemaを測ってください。sqlite3 CLIがエラーのあとに何をするか、制約違反がトランザクション全体を元に戻すかを、ドキュメントで確認してみてください。採点ツールは、失敗の位置が異なるマイグレーションで、migrate.shを試験します。
残った-walまで処理するrestore.sh
引数がBACKUPとTARGETのスクリプトを作ってください(ファイル: /root/rehearsal/restore.sh)。壊れたバックアップは、対象に触れる前に拒否し、復元のあとで、内容ハッシュがバックアップと同じことを確認します。
対象の隣に-walが残っていると、次に開くときに何が起きるかを、WALのドキュメントでもう一度見てください。バックアップの検査は、対象に触れる前に、証明は、復元したあとに。採点ツールは、強制終了で-walが残ったDBと、ページが壊れたバックアップを渡します。
デプロイ中に強制終了された運用DBを元に戻す
liveapp.py crashのあとで、restore.shでlive.dbをbackups/pre-v2.dbに復元し、結果を残してください(ファイル: /root/rehearsal/evidence/restore.json)。
crashが終わったら、ls -l live.db*で、何が残ったかを見てください。障害のtokenは、app/events.logの最後のcrashedの行にあります。復元のあとで、hotfix-で始まる注文が残っているかも、数えてみてください。
一度に動くリハーサルと報告書
引数がDB・WORKDIR・UP.sql・BROKEN.sqlのスクリプトを作ってください(ファイル: /root/rehearsal/rehearse.sh)。WORKDIR/backup.dbと報告書を残し(報告書: WORKDIR/report.json)、すべての証明が合っているときだけ、0で終わります。
前に作ったmigrate.sh・restore.shを、スクリプトの位置を基準に呼んでください。失敗の注入が失敗しなかった場合、失敗のあとに内容ハッシュが変わった場合、復元のあとにバックアップと違った場合は、すべて0以外でなければなりません。採点ツールは、-walにだけコミットが残ったDBと、ランダムなバージョン番号で動かします。