一覧SD-13-3 · エラー・ログ・機密情報

エラー時の後処理とデータ削除

資源の解放とデータ削除を、実際の保存先ごとに確認します。

説明

処理が途中で失敗したときには、保存途中の値だけでなく、開いたファイルや一時領域も後始末の対象です。削除要求についても、本体の削除だけでcacheやbackupが消えるとは限りません。

一時ファイルを開いたまま例外を発生させ、context managerによる終了処理を調べます。次にSQLiteの実バックアップを使い、本体・cache・復元後のコピーを順に照合します。

資源の解放とデータ削除を、実際の保存先ごとに確認します。

1例外時にファイルが閉じられるか調べる

一時領域のファイルに書いた直後、固定例外を発生させます。

from sd13 import * with tempfile.TemporaryDirectory() as directory: path = pathlib.Path(directory)/"partial.txt" try: with path.open例外時にもファイルを閉じる("w") as handle: handle.write("partial") raise RuntimeError("fixed failure") except RuntimeError:print(handle.closed,path.exists()) print(path.exists())一時領域終了後の残存を調べる

例外後にファイルは閉じられますが、一時領域内ではファイル自体は存在します。領域を抜けると削除されます。closeとunlinkを同じ処理とは扱いません。

2本体・cache・backupを用意する

aliceとbobを持つDBから実バックアップを作成済みです。

lab = Deletion() print(lab.db.execute("SELECT * FROM profiles ORDER BY id").fetchall()) print(lab.cache) print(lab.backup.execute別DBへ複製した値を読む("SELECT * FROM profiles ORDER BY id").fetchall())

三つの保存先に同じ二人が存在します。どこから返却する機能かに応じて、削除の対象範囲が変わります。

3本体だけを削除する

id7の行を本体から削除します。

with lab.db:lab.db.execute("DELETE FROM profiles本体の対象行を消す WHERE id=7") print(lab.db.execute("SELECT * FROM profiles").fetchall()) print(lab.cache.get(7)別の取得経路を試す)

本体はbobだけですが、cacheはaliceを返します。DBのDELETE成功だけで、全取得経路の削除完了とは判断できません。

4cacheの対象も無効にする

同じIDのcacheだけを削除します。

lab.cache.pop対象IDのcacheを無効にする(7,None) print(lab.cache) print(lab.backup.execute("SELECT * FROM profiles").fetchall())

cacheはbobだけ、backupにはaliceも残ります。別利用者の行を巻き込まず、各コピーの保持規則を確認します。

5過去のbackupを復元すると削除前の値が戻る

別のインメモリーDBへバックアップを復元します。

restored = sqlite3.connect(":memory:") lab.backup.backup(restored)過去コピーを別DBへ復元する print(restored.execute("SELECT * FROM profiles ORDER BY id").fetchall())

aliceが再び存在します。復旧時にはバックアップ作成後の削除要求も再適用する手順が必要です。

6削除記録を復元先へ再適用する

固定の削除対象ID7を復元先へ適用します。

with restored:restored.execute("DELETE FROM profiles WHERE id=?復元後の対象行を再削除する",(7,)) print(restored.execute("SELECT * FROM profiles").fetchall()) restored.close()

復元先もbobだけとなります。削除要求の記録には、再適用に必要な識別子と保持期間を定めます。

7対象外のデータと保持中backupを照合する

本体とcacheのbobを保持し、backupの状態も明示します。

print(lab.db.execute("SELECT * FROM profiles").fetchall(),lab.cache) print(lab.backup.execute("SELECT COUNT(*) FROM profiles保持中コピーの範囲を数える").fetchone())

本体・cacheにはbob、保持中backupには2件です。論理DELETEは保存媒体からの完全消去とは別であり、backupの保存期限やアクセス制限と合わせて扱います。

まとめ

closeと一時ファイルの削除を別に確認します。

本体・cache・backupで削除の範囲を追います。

復旧時に削除要求を再適用する手順を用意します。

操作履歴と解説を振り返れます。

Python 3.15 / 固定の公開試料

1
from sd13 import * with tempfile.TemporaryDirectory() as directory: path = pathlib.Path(directory)/"partial.txt" try: with path.open("w") as handle: handle.write("partial") raise RuntimeError("fixed failure") except RuntimeError:print(handle.closed,path.exists()) print(path.exists())
True True
False
2
lab = Deletion() print(lab.db.execute("SELECT * FROM profiles ORDER BY id").fetchall()) print(lab.cache) print(lab.backup.execute("SELECT * FROM profiles ORDER BY id").fetchall())
[(7, 'alice'), (8, 'bob')]
{7: 'alice', 8: 'bob'}
[(7, 'alice'), (8, 'bob')]
3
with lab.db:lab.db.execute("DELETE FROM profiles WHERE id=7") print(lab.db.execute("SELECT * FROM profiles").fetchall()) print(lab.cache.get(7))
[(8, 'bob')]
alice
4
lab.cache.pop(7,None) print(lab.cache) print(lab.backup.execute("SELECT * FROM profiles").fetchall())
{8: 'bob'}
[(7, 'alice'), (8, 'bob')]
5
restored = sqlite3.connect(":memory:") lab.backup.backup(restored) print(restored.execute("SELECT * FROM profiles ORDER BY id").fetchall())
[(7, 'alice'), (8, 'bob')]
6
with restored:restored.execute("DELETE FROM profiles WHERE id=?",(7,)) print(restored.execute("SELECT * FROM profiles").fetchall()) restored.close()
[(8, 'bob')]
7
print(lab.db.execute("SELECT * FROM profiles").fetchall(),lab.cache) print(lab.backup.execute("SELECT COUNT(*) FROM profiles").fetchone())
[(8, 'bob')] {8: 'bob'}
(2,)

演習完了