The Hidden Cost of Keeping Everything Just in Case
Hatched by Jaeyeol Lee
Jul 04, 2026
9 min read
4 views
68%
The seductive lie of preservation
There is a comforting belief in software systems: if something might be useful later, keep it. Delete nothing. Break nothing. Preserve every row, every state, every migration path, every legacy assumption. It feels safe, responsible, even mature. Yet this instinct often creates the very fragility it is trying to avoid.
That is the deeper tension running through modern engineering: what looks like caution can become a kind of accumulated risk. Systems age not only because they change, but because they remember too much in the wrong way. A database table full of inactive records, a schema that never lets go of obsolete patterns, a codebase that preserves every compatibility workaround, all of these are forms of memory that have stopped serving the present.
The real question is not whether software should preserve history. It is how much of history deserves to remain structurally active. Once something is still part of the live operating surface, it is no longer a record. It is a liability with documentation.
Soft delete is not just a data choice, it is a governance choice
Soft delete seems harmless at first. Instead of removing a record, you mark it as deleted and keep it around. This solves immediate concerns: accidental removal, auditing, restoration, the comfort of having an escape hatch. But the design quietly changes the meaning of deletion itself. The system no longer has a clear boundary between what exists and what merely lingers.
That ambiguity spreads. Every query must remember to exclude dead rows. Every uniqueness constraint becomes trickier. Every reporting tool, admin screen, and background job needs to know which records are real and which are ghosts. What began as a local convenience becomes a global rule that every future component must honor.
This is the key insight: soft delete does not remove complexity, it redistributes it. Instead of concentrating the cost at the moment of deletion, it taxes every read, every join, every future feature. The system pays forever for a decision that was meant to be easy once.
Think of it like keeping expired inventory on the sales floor because it might be returned later. Sure, you can do it. But then every clerk, customer, and delivery worker must distinguish valid stock from unusable stock, every time, forever. The shelf has become a museum of near use.
The same logic applies to database schemas and production systems. When deleted data remains live, the absence of a clean cutoff encourages accidental dependence. Old rows become part of the mental model of the system, and soon they are no longer exceptional. They are just another category everyone must remember.
A deletion that does not delete is not a deletion. It is a policy for indefinitely carrying complexity.
Why large systems suffer most from incomplete change
At small scale, carrying extra complexity feels manageable. A handful of legacy rows, a temporary compatibility layer, a feature flag nobody touches, these are tiny costs. But systems do not stay small in the ways that matter. Their user base grows, their query patterns diversify, their maintenance surface expands, and every small exception becomes a bigger tax on coordination.
This is why major infrastructure work often centers not on adding power, but on restoring clarity. A difficult database upgrade, for example, is rarely just about new features. It is about making the operational contract legible again. With each major version change, assumptions get revalidated, defaults shift, edge cases disappear, and accidental behaviors are forced into the open.
That process is uncomfortable because it reveals how much of a system’s stability depends on inertia. Many systems appear stable not because they are well designed, but because they have not yet been asked hard questions. Upgrades force those questions. They expose hidden dependencies, stale practices, and implicit behaviors that had been quietly masquerading as architecture.
This is why the discipline of upgrading infrastructure and the discipline of deleting data are more alike than they first appear. Both are acts of reasserting boundaries. Both ask, what still belongs in the active system, and what should remain only in history, backups, or logs?
The engineering instinct to preserve everything can be especially dangerous in mature systems. Over time, teams become afraid to touch anything that might break something else. So they accrete workarounds instead of making clean decisions. The result is a system that feels reliable because it is brittle in familiar ways. It can survive the known failures, but it struggles with change itself.
And change is not an exception. Change is the environment.
The real tradeoff is not safety versus danger, it is local convenience versus systemic clarity
Soft delete is attractive because it offers immediate safety to the person making the change. If a record is not physically removed, it feels reversible. If an upgrade path preserves compatibility, it feels cautious. If a workaround avoids a migration, it feels efficient. These are all examples of the same deeper pattern: moving cost away from the present actor and into the future system.
That is not always wrong. Sometimes reversibility matters. Sometimes auditability matters. Sometimes the cost of strictness exceeds the benefit. But the crucial mistake is to treat every form of preservation as neutral. Preservation is not free. It has a shape. It creates secondary obligations.
A useful mental model is to ask: where does the complexity live after this decision?
If you soft delete a record, complexity lives in every reader. If you preserve an outdated schema shape, complexity lives in every writer. If you defer an upgrade, complexity lives in every future operational decision. If you keep obsolete behavior “just in case,” complexity lives in every person who has to remember the exception.
The system does not become simpler by refusing to choose. It simply becomes harder to reason about because its rules are no longer explicit. The cost of ambiguity is especially high in distributed systems, where different services, teams, and tools must all agree on what is true. A record that is simultaneously present and deleted invites subtle disagreement, and disagreement in software becomes bugs.
There is a reason mature systems often rely on hard boundaries, versioning, and explicit migration. These are not signs of harshness. They are signs of honesty. They acknowledge that a system cannot forever be all things to all states.
Good engineering does not try to preserve every possible future. It tries to make the current future legible.
A cleaner framework: memory belongs in the archive, not the operating model
The most useful way to connect these ideas is to separate history from operation.
History is what happened. Operation is what the system treats as presently true.
Soft delete blurs that line. So do lingering schema hacks, hidden compatibility layers, and upgrades delayed until they become emergencies. In each case, the system keeps history inside its operational logic. That makes the present harder to trust.
A better design principle is this: retain evidence, not live ambiguity. If the system needs auditability, store immutable events, logs, or archival records. If the system needs recovery, maintain backups and restoration paths. If the system needs transition, use explicit migrations and phased rollouts. But do not let the active model carry the full weight of every historical contingency.
This framework also clarifies when soft delete is acceptable. The question is not whether data can ever be restored. The question is whether a deleted entity should still participate in ordinary business logic. If not, then the system should treat it as gone, and preserve the evidence elsewhere. That distinction preserves the meaning of deletion while still protecting against loss.
The same principle applies to upgrades. A healthy system does not cling to every legacy behavior forever. It extracts, archives, and removes. It measures what is still needed, what is merely feared, and what is already obsolete. It understands that compatibility is a bridge, not a neighborhood.
In practice, this means building systems that can let go without forgetting. That sounds paradoxical, but it is the essence of maintainable software. You want the ability to reconstruct the past when needed, while keeping the present small enough to comprehend.
What this means for engineering teams
The hardest part of these choices is psychological. Engineers naturally prefer options that postpone irreversible decisions. Product teams want fewer support incidents. Operators want fewer surprises. Security teams want more traceability. All of these instincts are reasonable. The problem is that they often point in the same direction: keep everything, just in case.
That instinct needs a counterweight. Not a reckless embrace of deletion, but a discipline of asking whether a safeguard has become a dependency. A system becomes healthier when its boundaries are clear enough that people can tell what is active, what is archived, and what is simply remembered.
This is especially important as systems scale across organizations. The more teams there are, the more dangerous it becomes to rely on tribal knowledge about invisible exceptions. A soft delete that “everyone knows about” is not a design. It is a rumor with database support.
The best systems do not rely on memory to enforce meaning. They encode meaning directly. Deleted means deleted. Current means current. Deprecated means deprecated. Transitional means explicitly transitional. The less a system asks its operators to intuit intent, the more resilient it becomes.
That does not mean becoming rigid. It means being deliberate about where flexibility belongs. Flexibility belongs at the edges, in controlled migrations, in rollback strategies, in archival access. It does not belong in the core semantics of everyday operation.
Key Takeaways
- Preservation is not free. Every extra bit of retained live state adds future complexity somewhere in the system.
- Delete should mean delete. If something is no longer part of ordinary operation, keep its history elsewhere, not in the active model.
- Ask where the complexity moves. A good engineering decision does not eliminate cost, it relocates it deliberately.
- Use archives for memory, not the runtime. Audit logs, backups, and event histories preserve the past without confusing the present.
- Treat upgrades and deletions as boundary setting. Both are ways of deciding what the system is today, not what it used to be.
The deeper lesson: systems fail when they refuse to become simpler
The temptation to keep everything alive comes from a noble place. Engineers want safety, reversibility, and respect for the past. But software does not reward indefinite preservation. It rewards clarity.
A system that cannot let go eventually becomes a system that cannot move. The ghosts in the database, the compatibility layers in the stack, the delayed upgrades, and the never quite deleted records all tell the same story: the past has been granted too much authority over the present.
The goal is not amnesia. It is disciplined memory. Keep what must be remembered, but do not let it continue to act. The most robust systems are not the ones that preserve every trace of their history inside the live model. They are the ones that know the difference between a record and a responsibility.
Once you see that distinction, design choices that once seemed cautious start to look expensive. And choices that once seemed drastic, like actually deleting data or completing a hard upgrade, begin to look like acts of structural honesty.
That is the real reframing: a healthy system is not one that keeps everything alive. It is one that knows what deserves to live, what deserves to be archived, and what must finally be allowed to end.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣