バックアップの世代管理と復元
破損の検出と過去状態の復元に必要な情報を確かめます。
説明
複製は更新を別の場所へ反映する仕組み、世代保存は過去の状態を残す仕組みです。ハッシュは内容の一致を調べるために使えますが、それだけで失った内容を元に戻せるわけではありません。
二つのファイルを持つ小さなデータ集合を複製し、更新と誤削除を反映します。保存しておいた世代から復元した後、一文字の破損をハッシュで検出し、照合用情報にも信頼できる保存先が必要なことを確認します。
1内容の複製と過去の世代
注文数と税率を持つ二つのファイルを、バイト列の辞書で表します。現在の複製と、時点を固定したv1を作ります。
from hashlib import sha256内容を照合する準備; live = {"orders.csv": b"count,10\n", "tax.txt": b"0.10\n"}現在の二つのファイル; replica = dict(live); versions = {"v1": dict(live)}複製と過去の版; print(live)初期内容この例の値は変更不能なbytesなので、辞書をコピーすると後のキー更新が別の辞書に波及しません。実際のスナップショットが必ず全バイトを即座にコピーするわけではなく、COW等で共有する実装もあります。
2更新を複製へ反映
注文数を12へ更新し、現在の内容を複製先にも反映します。
live["orders.csv"] = b"count,12\n"注文数を更新; replica = dict(live)最新状態を複製; print(replica["orders.csv"], versions["v1"]["orders.csv"])現在と過去の内容複製先は12、v1は10です。複製が最新状態を保つことと、過去版を残すことは別です。遅延や保持方針によって復旧できる時点は変わります。
3誤削除も複製へ反映される
更新後の正常な状態をv2として残してから、注文ファイルを誤って削除し、その削除も複製します。
versions["v2"] = dict(live)削除前の世代; del live["orders.csv"]注文ファイルを削除; replica = dict(live)削除を複製; print(sorted(live), sorted(replica), sorted(versions["v2"]))残った名前を比較現在と複製先にはtax.txtだけ、v2には両方が残ります。同一障害で世代保存先も失われる構成なら復旧できないため、保存場所、権限、保持期間も合わせて設計します。
4復旧する時点を選ぶ
削除前のv2から復元し、古いv1を選んだ場合の注文数と比較します。
restored = dict(versions["v2"])削除直前の版を復元; print(restored["orders.csv"], versions["v1"]["orders.csv"])一つ前の世代との差v2なら12、v1なら10に戻ります。復元する版によって、戻せる更新の範囲が異なります。バックアップ後の更新も復旧するには、追加のログなどが必要になる場合があります。
5正常な内容のハッシュを記録
正常なv2の各ファイルからSHA-256を計算します。ここではこの対応表を別の信頼できる場所で保持するものとします。
manifest = {name: sha256(body).hexdigest() for name, body in versions["v2"].items()}正常な内容から一覧を作る; print(*sorted(manifest.items()), sep="\n")名前とハッシュを表示256ビットのハッシュを64桁の16進数で表示しています。ファイル名とハッシュの対応があるので、内容が期待した版と一致するかを調べられます。ハッシュ値だけにはファイル本体は保存されていません。
6一文字の変化を検出
復元した注文数が12から92に変わった場合を作り、二つのファイルを照合します。
restored["orders.csv"] = b"count,92\n"一文字を変更; checks = {name: sha256(body).hexdigest() == manifest[name] for name, body in restored.items()}基準のハッシュと比較; print(checks)不一致のファイルorders.csvだけFalseです。内容が期待値と異なることは分かりますが、破損箇所や正しい内容そのものはハッシュから得られません。偶発破損か意図した変更かも、この照合だけでは決まりません。
7照合表も置き換えられた場合
不正な内容に合わせて照合表まで作り直された場合を計算します。
replaced_manifest = {name: sha256(body).hexdigest() for name, body in restored.items()}変更後の内容で基準を置換; print(all(sha256(body).hexdigest() == replaced_manifest[name] for name, body in restored.items()))一致だけでは正当性を示せない例Trueですが、正しいv2へ戻ったわけではありません。比較する双方が同時に変えられれば一致してしまいます。信頼できる保存先や署名等によって、基準となる情報の真正性も確保する必要があります。
8正常な世代から復元して再照合
元のv2から注文ファイルを戻し、別に保持した元のmanifestで照合します。
restored["orders.csv"] = versions["v2"]["orders.csv"]正常な内容へ復元; print(restored["orders.csv"])復元した値; print(set(restored) == set(manifest), all(sha256(restored[name]).hexdigest() == expected for name, expected in manifest.items()))名前と全内容を照合ファイル名の集合と全内容が基準に一致しました。実際の復旧では、このバイト単位の一致に加え、アプリケーションが開けるか、関連ファイルの時点が整合するか、必要時間内に復元できるかも確認します。
まとめ
最新状態の複製と、過去の世代を残すことは別の役割です。
ハッシュは不一致の検出に使えますが、復元には正常な内容が必要です。
復旧する版、基準情報の信頼性、関連データの整合と復旧時間まで確認します。
操作履歴と解説を振り返れます。
Python 3
from hashlib import sha256; live = {"orders.csv": b"count,10\n", "tax.txt": b"0.10\n"}; replica = dict(live); versions = {"v1": dict(live)}; print(live){'orders.csv': b'count,10\n', 'tax.txt': b'0.10\n'}live["orders.csv"] = b"count,12\n"; replica = dict(live); print(replica["orders.csv"], versions["v1"]["orders.csv"])b'count,12\n' b'count,10\n'
versions["v2"] = dict(live); del live["orders.csv"]; replica = dict(live); print(sorted(live), sorted(replica), sorted(versions["v2"]))['tax.txt'] ['tax.txt'] ['orders.csv', 'tax.txt']
restored = dict(versions["v2"]); print(restored["orders.csv"], versions["v1"]["orders.csv"])b'count,12\n' b'count,10\n'
manifest = {name: sha256(body).hexdigest() for name, body in versions["v2"].items()}; print(*sorted(manifest.items()), sep="\n")('orders.csv', '96691ef74ab1ae5b3a8d1a667946ba05fac43fc5d60c48f0a573c9a6eb5bb429')
('tax.txt', '0ee15e911b924ccc416e129dc5b75bdfc620136a0d5e449d3c61dd421f0c9395')restored["orders.csv"] = b"count,92\n"; checks = {name: sha256(body).hexdigest() == manifest[name] for name, body in restored.items()}; print(checks){'orders.csv': False, 'tax.txt': True}replaced_manifest = {name: sha256(body).hexdigest() for name, body in restored.items()}; print(all(sha256(body).hexdigest() == replaced_manifest[name] for name, body in restored.items()))True
restored["orders.csv"] = versions["v2"]["orders.csv"]; print(restored["orders.csv"]); print(set(restored) == set(manifest), all(sha256(restored[name]).hexdigest() == expected for name, expected in manifest.items()))b'count,12\n' True True
演習完了