Skip to content

Backend & Distributed Systems

Tombstones for Delete and Reseed Safety

How to permanently delete user-visible records while preventing startup seed logic from recreating the same unwanted item.

Published 2 min read
#backend#sqlite#deletion#data-integrity

Seed data is useful for first-run setup, but it can fight user intent. A common failure is: the user deletes a seeded task, the application restarts, and initialization inserts it again because the row is missing. The visible record should be permanently deleted, while a small tombstone records that this seed must not return.

Diagram loads as it approaches the viewport.

Separate retention from suppression

A universal soft-delete column keeps the entire record, including notes that the user expected to remove. This design deletes the task row and stores only the minimum suppression key and deletion time:

sql
CREATE TABLE task_tombstones (
  title TEXT PRIMARY KEY,
  deleted_at TEXT NOT NULL
);

Startup insertion then requires two negative conditions: no live task with that title and no matching tombstone. The tombstone is not a hidden copy of the task and contains no note body.

Transactions protect intent

Deleting the row and writing the tombstone must succeed together. If the process crashes between the two operations, the next startup can resurrect the item. A transaction or a database batch gives the two statements one commit boundary.

The suppression key also needs a stable definition. Titles work for a small fixed seed catalog, but a larger product should assign immutable seed IDs so wording changes do not bypass the tombstone.

End-to-end evidence matters

The production-like test created a uniquely named task, reloaded the API, modified status and notes, deleted it, and reloaded again. The task count returned to its original value and the known academic item stayed intact. The temporary tombstone remained, which is the intended evidence that deletion survives initialization. This pattern is useful when bootstrap defaults exist, but it should not be applied blindly to user-created records that have no reseeding path.

Related writing