The problem was not disk space
Blocklist snapshots in Koru Shield had grown to 419 rows. That was not just a storage number. Every extra snapshot was another version to reason about, another candidate for a stale read, and another thing a future change had to account for.
The fix was not to buy more room. It was to decide what a snapshot is for, and what it is not for.
Keep the recent, let the rest go
The pruning rule kept the most recent snapshots per blocklist and removed the rest. After pruning, the count went from 419 rows to 36, with about 1.2 GB reclaimed.
That part is easy to report. The harder part is the rule behind it: expired is a terminal state, and a pruned snapshot does not come back. If a retention rule can quietly resurrect old data, it is not a retention rule. It is a suggestion.
Version consistency made the change safe to verify
Every deployable component in Koru Shield self-reports its running commit, so a pruning change can be checked against the version that is actually running, not the version that was intended. That matters when the change deletes data. You want proof of what ran, not just a plan for what should run.
A related lesson from the same system: an API container that exited with code 137 was not fixed by adding memory and hoping. Right-sizing it brought memory use from 74 percent to 29 percent, which is a reminder that an out-of-memory exit is a signal about sizing and workload, not just a reason to scale up.
What I would do again
- Define retention before storage grows, not after it hurts.
- Make expired states terminal, so pruning cannot be undone by a later code path.
- Verify the running version after a destructive change, not just the deployed version.
- Treat a smaller, legible dataset as a feature, not as lost history.