Learn

Architecture & pattern guides

Read the idea, build it on a Grid, prove it in a challenge. Short, practical guides — illustrated with RodGrid's own nodes — on system design, design patterns, flow modeling, and how to get the most out of the app.

All articles

Deleting a person's data: erasure across every copy

Why a delete button removes one copy of a person and the rest stay: listing every store and every key the person lives under, a resumable fan-out with its own order, derived copies that refill themselves, backups and archives that cannot drop a row (expiry with a deletion log replayed after restore, or crypto-shredding and its limits), what the law lets you keep, outside providers, and a completion record that holds none of the erased data.

A year in review for a deleted account

The email was titled Your year in running, and it went to a runner who had deleted her account in March. She had opened the app's settings, pressed Delete account, and the button had done what its code said: DELETE FROM users WHERE id = 77041, with the foreign keys cascading to her runs, follows and comments.

Two copies the button never touched sent it. The year-in-review job reads the analytics warehouse, which files her runs under her phone's id, not her account's. The email provider's contact list still had her address. The delete had removed the one copy its author was thinking of.

Where one runner lives

Follow her data through the system:

  • the accounts database: profile, email address, runs, follows;
  • object storage: the GPS track of every run, one file each;
  • the search index that finds people and clubs by name;
  • a Redis cache of profiles and leaderboards;
  • a Kafka topic of activity events, kept for seven days;
  • the warehouse, keyed by device id;
  • application logs, some of which printed her email address;
  • 35 days of nightly database backups;
  • two outside companies, the email provider and the payment provider.

Erasure is removing one person's personal data from every place the system copied it, including places built never to delete anything, and being able to show that it was done. In the EU, Article 17 of the GDPR gives people that right, with exceptions, among them data the company is legally obliged to keep. The erasure is due without undue delay, and Article 12(3) requires the company to tell the person what it did without undue delay and within one month of the request, extendable by two further months where the complexity or number of requests makes it necessary, if the person is told of the extension, with the reasons, within the first month. The law sets the floor; what a company keeps, and why, is decided with its lawyers.

You can only erase what you can list

The warehouse was missed because no list said it held people. An erasure run reaches only the stores the data catalog knows about, so it depends on that catalog being complete, the subject of Data governance: catalogs, lineage & classification; automated discovery is a safety net for what nobody registered. What erasure needs from each entry is narrow: what personal data the store holds, under which key, and how a deletion is done there, by account id in the database, by device id in the warehouse, by email address at the email provider.

The keys matter as much as the stores. A run first resolves every identifier the person has, account id, email, device ids, the payment provider's customer id, and only then deletes anything: the row that maps her account to her phone lives in the accounts database, and once it is gone her warehouse rows are impossible to find and no less hers.

Ten stores, ten ways to delete

Each store has its own deletion path, so the run fans out into one step per store, each with a status: done, retrying, waiting for confirmation, pending expiry, failed. Order matters in three places:

  • identifiers are resolved before the rows that map them are deleted;
  • child rows go before their parents where the foreign keys do not cascade;
  • a copy is erased after the store it is filled from, or the next refill puts her back: a cache cleared before the database refills on the next read, a warehouse table cleaned before the nightly load copies her in again. A refill or event already in flight can still land after the clear, so before the derived copies get a last check, the run blocks new writes for her account, has consumers drop events for an erased account, and waits for operations already in flight to finish or fences them off from writing.

Some steps finish in seconds; the warehouse delete runs in a weekly batch. A run that lasts days must survive deploys and crashes and resume where it stopped, every step safe to repeat; that machinery is Durable workflows: orchestration that survives restarts. A synchronous delete inside the request is simpler, until the sixth of ten stores times out and nothing records which five were done. Two of the ten belong to other companies, the email and payment providers: the run calls each one's deletion API, and that store is not done until the run holds the provider's confirmation, which can arrive days later.

One request, every copy

The catalog says where the runner lives and under which key; the engine keeps a status per store, and the workers delete.

Copies that come back

Derived copies each fail in their own way. The cache expires on its own if every entry has a TTL, but deleting her keys is cheap and stops her profile being served until then. The search index keeps her documents until told to delete them or rebuilt without her. The warehouse needs an explicit delete, and in a table format with time travel her runs stay readable in earlier versions until those are expired. The Kafka topic and the logs only append: her entries leave when they age past retention, which is the argument for keeping email addresses out of logs.

The backup that cannot forget

Backups exist to keep the past, write-once archives so that nothing can change it, and the runner is in 35 nightly snapshots. Rewriting each one is costly and works against the reason they are kept, and an archive under a compliance-mode object lock refuses outright. That leaves two ways out: one is slow, the other must be planned early.

The first is to let the copies expire on their normal rotation. Until the last snapshot holding her ages out, 35 days here, the data is kept beyond use: the backups are kept out of everyday use, reachable only through a controlled restore. The danger is the restore: bring the database back from last Tuesday and the runner is back, email address and all, in time for next week's newsletter. So the run appends her internal account id, and nothing else, to a deletion log kept outside the backups, and every restore replays that log before the restored store serves traffic. Restores themselves belong to Backups, high availability & disaster recovery.

The second has to be chosen before her first run is recorded. Crypto-shredding encrypts each person's data under a key of their own, held in a key store, and erases it by destroying that key: the ciphertext stays in every backup and archive, and nobody without a copy of the key can read it. The envelope pattern behind it is in Secrets management & encryption. It is a common answer for an event-sourced system too, whose events are the record and are not rewritten (Event Sourcing).

Its limits sit exactly where the copies are. It covers only data encrypted under her key when it was written: four years of GPS files stored in plaintext are not rescued by a key created today. It covers nothing decrypted and copied elsewhere: the warehouse, the search index, an export someone ran last spring. The key store must never lose a key by accident and must lose this one on purpose, in its own backups too, where a surviving copy undoes the shredding. And the ciphertext is still there; what is gone is the means to read it.

What is allowed to stay

She paid for a premium plan for two years, and tax law obliges the app to keep its invoices for years. For those records the legal obligation wins over the erasure request. That is no licence to keep everything: keep the invoices and their billing details, not her runs; restrict who can read them; and write down which obligation applies and when it ends, so they are deleted then. Audit trails meet the same conflict from the other side, and Audit logs & tamper evidence works through it.

Two shortcuts do not count. A deleted_at flag hides the row from the screens and removes it from nothing. Replacing her account id with a hash in the warehouse leaves rows that anyone holding the mapping, or able to recompute the hash, can link back to her: still personal data, and her GPS tracks, starting and ending at the same front door every morning, identify her with or without an id.

A receipt with nothing in it

At the end the company needs something to show a regulator, and the runner has to be told within the deadline. The completion record lists the request id, when it arrived and was verified, each store with its status and finish time, each provider's confirmation and the records kept, with the reason. It also says which of three states the erasure has reached: gone from the live systems; residual backups isolated, awaiting expiry on the date the last one holding her ages out; or final expiry, with no backup holding her left. It does not list her name, email address or runs: a proof of erasure that stores the erased data is one more copy to erase. Keep it where it cannot be quietly edited, and send her the summary, that date included.

The requirement with no row to delete

The GDPR Erasure Pipeline Challenge asks for this system, from the verified request to a completion certificate the company can show a regulator, with analytics, backups and third-party processors in the estate. Its requirement to handle stores that cannot delete a single row is where the runner's case is decided: your design has to say whether she leaves the backups by expiry, with a deletion log replayed on every restore, or by a destroyed key, and which of her records it keeps, and why.

Practice this