Why We Stopped Using Soft Delete Everywhere
Building Droplox — Engineering Notes #8
In the early stages of development, Soft Delete often seems like the perfect solution. Instead of physically removing a record from the database, you simply add a deleted_at or is_deleted field and exclude those records from normal queries. Users can restore deleted data, historical information is preserved, and the risk of accidental deletion appears minimal.
That is why many teams begin using Soft Delete almost everywhere.
We followed the same path.
While Droplox was still a relatively small platform, this approach worked without any noticeable issues. However, as the system grew, it became clear that what initially seemed like a universal solution was gradually becoming a source of unnecessary complexity.
The first problem was surprisingly simple. Every query now had to remember that deleted records existed. Almost every data retrieval operation required an additional condition such as deleted_at IS NULL. At first, this looked insignificant. Over time, however, these checks appeared in nearly every repository, service, and newly developed feature. Forgetting a single condition could expose data that users had considered deleted long ago.
Other consequences gradually emerged as well. The database continued storing large numbers of logically deleted records, making indexes less efficient. Queries became more complex, analytics required additional filtering, and relationships between tables became less intuitive than before. This was particularly noticeable when one entity had already been deleted while related records continued appearing in query results.
Eventually, we realized that Soft Delete itself was not the problem.
The real issue was that we had adopted it as the default strategy without asking whether it actually made sense for each type of data.
After that, our approach changed.
Instead of searching for one universal solution, we began evaluating the lifecycle of each entity individually.
If data is important for auditing, legal compliance, historical tracking, or recovery, Soft Delete is completely justified.
If a record is temporary and has no value after deletion, a physical delete is usually the better choice.
If information may be needed in the future but should no longer remain in operational tables, archiving is often the most appropriate solution.
This change significantly simplified our architecture. Operational tables stopped accumulating large volumes of unused records, queries became easier to understand, and the risk of accidentally exposing deleted data was greatly reduced.
The experience reminded us of another important lesson.
In software architecture, there are very few universal solutions.
What works perfectly for one entity may be entirely unnecessary for another.
That is why, when designing new components today, we begin with a simple question:
“What should happen to this data after it is deleted?”
The answer is not always the same.
But it usually leads us to the right strategy.
We try to apply the same principle throughout the rest of the platform as well. Instead of relying on universal patterns, we choose architectural approaches that genuinely fit the specific problem. We discussed this philosophy in more detail in our article explaining why internal tools deserve the same level of architectural attention as customer-facing features:
This article also continues our first engineering note about how the product catalog architecture evolved as the platform grew:
Conclusion
Soft Delete remains a valuable tool—when used intentionally.
It works extremely well where preserving history or enabling data recovery is genuinely important.
However, when applied automatically to every entity, it gradually makes queries more complex, introduces hidden dependencies, and reduces the overall transparency of the system.
Over time, we reached a simple conclusion:
There is no universal strategy for deleting data.
Good architecture does not begin with choosing between Soft Delete and Hard Delete.
It begins with understanding what role that data should play after the user clicks “Delete.”
