資料刪除層:缺乏事前紀錄時,刪除與資料遺失難以區分
The Deletion Layer — Or, "We Deleted It" and "We Lost It" Are the Same Sentence Until You Can Prove Which One Happened
沒有事前刪除紀錄時,外界難以分辨資料是按政策刪除還是因故遺失,兩者留下的證據同樣是空白。作者建議預先盤點所有存放位置,並在刪除前建立收據,記錄資料雜湊值、存放位置、時間及政策依據、刪除方法、驗證結果和核準者。備份副本須記錄到期日期;衍生資料和法律保留則要釐清刪除範圍及中止機制,文章亦提供以單一資料類別試行的步驟。
There is a question that arrives in every incident, audit, and customer security review, and it arrives in the same four words: where is the data now.
Most teams answer it with a sentence they believe to be true. We deleted it. We removed it from production. It was purged when the account closed. The sentence is usually accurate about the system the speaker is looking at, and it is usually wrong about the data, because "the data" is not in one place and never was.
That is not a scandal. It is the ordinary state of a system built by people who were solving different problems in different years. The problem is that we deleted it and we lost it produce identical evidence from the outside: nothing. The only thing that distinguishes them is a record you had to create before the deletion, and almost nobody does.
Delete is a pointer operation
When you delete a row, you generally free a slot and stop pointing at it. The bytes may sit in a page until it is reused, in a write-ahead log until it is checkpointed, in a replica until replication catches up, in a nightly backup until the backup expires, in a cache until TTL, in a search index until re-index, in an analytics warehouse that ingested it last month, and in an embedding store where it stopped being a row and became 1,536 numbers.
Every one of those has its own clock. So the honest answer to "when will this be deleted" is not a date. It is a maximum over replicas:
deletion_complete = max(primary, replicas, backups, caches, derived stores, vendor copies)
Which means the number most teams quote — "we delete it after 30 days" — describes the fastest clock in the system, not the slowest. The slowest one is usually a backup: the row is gone from the app in a second and present in a tape, a snapshot, or a vendor's cold storage for months. It has been "deleted" and it has not been destroyed. Those are different verbs and the difference is the whole subject.
The three ways data disappears, from outside
| How it vanished | What you have afterwards | What it looks like from outside |
|---|---|---|
| Deletion under policy | A schedule, a hash, a receipt | Nothing — unless the schedule and receipt exist |
| Expiry (retention job) | The job's config, if anyone kept it | Nothing |
| Loss (failure, migration, vendor change) | An incident timeline, maybe | Nothing |
All three leave an empty chair. An accuser does not need to prove which one happened; they only need to point at the chair. That asymmetry is why deletion disputes are so often lost by the party that was actually correct: the party that deleted properly has no artifact, and the party that lost the data also has no artifact, and the only difference between them lives in a ticket that was never written.
The fix is not "keep more data." Keeping everything is not a legal option in most jurisdictions, it is not a security posture, and it is not what your customers were promised. The fix is to make the deletion itself a producible event.
Deletion is revocation, for data
This is the same shape as a certificate problem: you revoke a credential, and the question immediately becomes who still trusts it, and until when. You delete a record, and the question is which copies still hold it, and until when. Same propagation, same lag, same blindness at the edges.
Which suggests the same design:
- Enumerate the stores before you delete, not after. If your deletion procedure starts with "delete from the users table," you have enumerated one store out of nine.
- Propagate deliberately. A deletion that depends on a cache TTL is a deletion scheduled by an unrelated team.
- Verify at the edge. Not "did the API return 404" but "does a query against the derived store return zero rows." Verification is a separate job from the deletion, run by something that does not share the deletion's assumptions.
A team that can answer "which systems held this record, which of them have confirmed zero, and when" is doing something rarer than it sounds. Most teams can answer the first third.
The deletion receipt
One line per deletion event, written before the deletion runs, not after. Six fields:
| Field | Why it exists |
|---|---|
| What (hash of the artifact or the record set) | Identifies the thing without keeping the thing |
| Where (every store, named) | Turns "we deleted it" into an enumerable claim |
| When (UTC, and the policy basis) | Establishes that the schedule predates the request |
| Method (row delete / crypto-shred / media destroy) | Different claims with different strength |
| Verification (query, result, timestamp) | The part everyone skips |
| Who / why (approver, policy clause) | The part that makes it an institutional act, not a personal one |
The hash is the trick that makes the whole thing possible. A SHA-256 of the export is not the data; it cannot be read, reconstructed, or misused. But it can be compared later: this is the artifact we held, this is the artifact we deleted, and this timestamp says both facts existed at a known moment. You keep the fingerprint and destroy the hand.
Which produces the uncomfortable part, and it is worth stating plainly: you cannot prove a deletion without retaining something. The tombstone is data. The honest position is not "we retain nothing" — it is "we retain the smallest thing that can prove the rest is gone," and then you write down that this is what you are doing, so it does not look like an oversight when someone finds the hashes in eighteen months.
Where deletion gets hard on purpose
Three cases that break naive procedures, in ascending order of how much they cost to get wrong:
Backups. Deleting from the primary does not touch the backup chain, and most backup systems cannot delete a single record without rewriting the set. The realistic procedure is: stop the record from being restored forward, document the backup expiry date, and write that date in the receipt. Not "deleted" — "removed from production immediately; unrecoverable after 2027-03-01 when the containing backup set expires." That sentence is defensible. "Deleted" is not.
Derived data. Aggregates, embeddings, and trained artifacts do not contain the record and cannot be deleted row-by-row. Either you can describe why the derived form is not personal data, or you cannot, and if you cannot, the deletion has a scope problem that no amount of engineering closes. Say which one it is in writing.
Legal hold. The requirement is not "deletion stops." The requirement is that deletion can be shown to stop — because a policy that deletes on schedule is only safe if there is a mechanism proven to interrupt it. Untested interruption is the same category as an untested backup: a belief, not a capability.
The Friday version
Pick one record class you have promised to delete — a closed account, an expired trial, a support transcript. Do this once:
- Export one instance with a date in the filename, hash it, and store the hash where the data used to be.
- List every store that holds a copy. Not the ones you think — the ones you can name. Count them. The count is the finding.
- Delete from the ones you control, note the ones you don't, and write the receipt line with the exact date each remaining copy expires.
- Run a query against a derived store and record what it returns. If you cannot run the query, that is the finding.
- Set a calendar entry for the backup expiry date. That is the day the claim becomes true.
Total: an afternoon, once per record class. The output is a sentence you can say in a review without hoping nobody checks: the record left production on this date, the copy in backups expires on that date, and here is the hash that proves both.
The part that should make you uneasy
Deletion policy is usually written by the party that benefits from fast deletion and enforced by the party that benefits from keeping evidence, and those two are in different departments that meet roughly annually. The result is a policy that is fast where it should be careful and slow where it should be quick.
The direction of the asymmetry is worth naming too: the entity accusing you almost never has a retention limit, and you almost always do. Which means the surviving record of a dispute is systematically the version held by the party making the claim. A deletion you can prove is the only thing that balances that, and it is boring work done on a day when nothing is wrong.
That is the whole thesis of this series, arriving for the fourth time in four weeks: the artifacts that save you are the ones you made when nobody was asking.
Next: the disclosure layer — because "we told the affected parties" and "we told the affected parties something true and specific" are, once again, the same sentence from the outside.
If you are holding a deletion promise you cannot currently prove — a customer asking "is it really gone?", a backup set that outlives the policy that governs it, an incident where "we deleted it" and "we lost it" would look identical on paper — say so in the comments. We keep a one-page deletion receipt (what, where, when, how, who signed off) for exactly that, and I will send it over. No form, no email list.
來源:DEV Community · dev.to