Suppose a comics database shows each series with a count of its issues, and the count is cached so the page doesn’t have to total thousands of rows on every view. The number looks harmless. But the moment someone asks whether a variant cover is an issue, the cached count stops being a number and becomes a business rule.
Here is a fictional example. A series has three base issues. Issue 2 also has a variant cover, filed under the same series. Issue 3 has a variant that is filed under a different series (which happens with things like Marvel’s Premier Classics line, where the variants form their own numbered series while the base issues are unnumbered singletons). How many issues does each series have?
| Record | Count in its own series? | Count in the other series? |
|---|---|---|
| Base issue | Yes | Not applicable |
| Variant in the same series | No; it belongs to its base issue | Not applicable |
| Variant in a different series | Depends on the rule | Depends on the rule |
The first two rows are easy, but the third row is where someone has to decide, and that decision should be determined somewhere other than the code that happens to calculate the total.

It matters because a cached count is usually calculated in two ways. An incremental update adjusts the total whenever a record is added, edited, or linked to another one. A bulk rebuild recalculates every total from scratch, often after a migration or when someone suspects the numbers have drifted. If the two paths implement even slightly different versions of the rule, the count depends on how it was last calculated. The page looks fine until someone runs the rebuild, and then a few hundred series quietly change.
The differences tend to hide in small places. The incremental path knows what just changed, so it handles the relationship that triggered it. The rebuild sees the final state and has to infer the same thing from a query. If that query depends on the order rows come back, whether from an Object-Relational Mapper (ORM) default or a database that sorts differently than the one the developer tested on, the rebuild can disagree with the incremental result without any error at all. Both paths pass their own tests, and the totals still don’t match.
I’d treat reconciliation as part of the feature, not as cleanup afterward:
- Write the rule as a short table, like the one above, before touching the code.
- Build a small set of records covering every row of that table, including relationship changes: a variant moved to another series, a variant promoted to a base issue, a link removed.
- Apply those changes through the incremental path, then run the rebuild, and compare the two sets of totals.
- Run that comparison on the database you deploy to, not only the one that is convenient locally.
If the two disagree, the table tells you which one is wrong. Without the table, you only know they disagree.
This is the kind of problem that can be fixed in pieces. The first change implements the rule; a later contributor, working against a different database, finds a case the first one missed. That isn’t a failure of the first change so much as an argument for the comparison test, which would have found it earlier.
A cached number saves time, but only if everyone agrees on what it counts. Decide that first, and write it where both calculations can be held to it.
—jhunterj