0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

データの取りこぼしを防ぐSQLiteバックアップと復元確認

0
Posted at

1行INSERTしてcommitしたのに、コピー先は0行。しかも integrity_checkok だった。SQLiteのバックアップは、ファイルが開けただけで成功扱いすると危ないんだよね。

自分は今回、WALモードで本体コピーと Connection.backup() を比べた。復元後の中身まで確認したい。

Q. commitした行は、どこへ消えた?

WALに更新が残る条件では本体コピーは0行、接続経由のバックアップは1行となり、どちらも整合性検査は通る図

元のDBには残っている。確定した更新はWAL(Write-Ahead Log)側にあり、本体のコピーでは拾えなかった。

WALモードでは更新をログに書き、チェックポイントで本体へ反映する。今回は自動チェックポイントを無効にし、空のテーブルを本体へ反映してからINSERTする。接続も開いたまま。

失敗を再現する実験用の設定なので、運用DBへの適用は避けたい。

Q. 本当に壊れていないのに、データが足りない?

wal_backup.py として保存し、python3 wal_backup.py で実行。Python 3.14.6/SQLite 3.53.4で確認した。追加パッケージは不要。DBは終了時に削除される。

import shutil
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory

def inspect(path):
    with closing(sqlite3.connect(path)) as db:
        return (
            db.execute("SELECT count(*) FROM jobs").fetchone()[0],
            db.execute("PRAGMA integrity_check").fetchone()[0],
        )

with TemporaryDirectory() as tmp:
    root = Path(tmp)
    live = root / "live.db"
    with closing(sqlite3.connect(live)) as src:
        assert src.execute("PRAGMA journal_mode=WAL").fetchone()[0] == "wal"
        src.execute("PRAGMA wal_autocheckpoint=0")
        src.execute("CREATE TABLE jobs(id INTEGER PRIMARY KEY)")
        src.commit()
        src.execute("PRAGMA wal_checkpoint(TRUNCATE)")
        src.execute("INSERT INTO jobs VALUES (1)")
        src.commit()

        # 接続を開いたまま、確定済みの更新をWALに残す
        shutil.copyfile(live, root / "copy.db")
        with closing(sqlite3.connect(root / "backup.db")) as dst:
            src.backup(dst)

        copied = inspect(root / "copy.db")
        backed = inspect(root / "backup.db")
        print("file copy:", copied)
        print("backup:   ", backed)
        assert copied == (0, "ok")
        assert backed == (1, "ok")

実行結果はこうなった。括弧内は行数と検査結果。

file copy: (0, 'ok')
backup:    (1, 'ok')

コピー先は空のテーブルとして整合している。integrity_check が通っても、期待した更新まで入っているとは限らない。この0行と ok の組み合わせは、地味に怖い。

Q. 何を変えれば、取りこぼしを防げる?

src.backup(dst) で接続経由のバックアップを作る。こちらはcommit済みの1行を含んでいた。保存先は閉じてから開き直して確認した。

本体と -wal の個別コピーは、途中で更新が進むと時点をそろえる問題が増える。今回は書き込みを止めて比較した。継続的な書き込み中の待ち時間や負荷は未測定。

ファイル名に日付が入っていると、つい安心してしまう。中身が0行でも名前は立派なんだよな。

Q. 復元確認は何を見ればいい?

assertは実験用。実務では必要なテーブルを読み、保存したい処理IDの存在まで確認する。件数が同じでも別の行が欠けるケースはある。

どの処理まで残れば合格かを先に決める。検証用の復元先で、その条件に沿って照合する。

おわりに

今回は0行と1行に分かれた。整合性検査は両方とも通るので、復元した内容も確認したい。まず残すべき処理IDを1つ照合する。履歴が増えても、この確認をバックアップ処理の完了条件に残しておく。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?