Catching Overflow Before It Corrupts an Imported Running Total
Cumulative counters, such as total units shipped or total page views, are often imported from other systems. A counter has a limit. Past it, a value may wrap to a small number, saturate, or fail, depending on the storage involved. A silent wrap looks like a reset and can corrupt every total built on it. This article proposes a conditional design. Check each assumption against your own storage and data.
The primary artifact: a counter boundary and overflow fixture
First write down the boundary for each counter: the maximum value your storage can hold and the maximum your business could plausibly reach. Then keep a fixture, a small table of inputs and expected outcomes. Columns:
- previous_value
- incoming_value
- increment_or_absolute: whether the import carries a delta or a new total
- expected_outcome: accepted, rejected_overflow, rejected_decrease, rejected_invalid
- reason
Compute the sum in a way that cannot itself overflow, for example by checking that previous_value is at most the maximum minus the increment before adding.
Decision branches
- Value is not a whole non-negative number (text, blank, fractional, negative): rejected_invalid.
- Delta import where previous plus increment would exceed the maximum: rejected_overflow. Store nothing partial.
- Absolute import that is lower than the previous value: rejected_decrease. A cumulative counter should not fall. It could be a reset or a wrap, and a person decides which.
- Absolute import above the maximum: rejected_overflow.
- Otherwise: accepted.
A rejected row is kept in a rejection list with its raw text, and the stored counter keeps its previous value.
Worked hypothetical example
Suppose the counter's maximum is 255, a deliberately tiny figure to make the fixture readable. It is not a recommendation.
- Row 1: previous 200, delta 50, sum 250: accepted.
- Row 2: previous 250, delta 10, sum 260: rejected_overflow, counter stays 250.
- Row 3: previous 250, absolute 40: rejected_decrease, because it could be a wrap.
- Row 4: previous 250, value abc: rejected_invalid.
- Row 5: previous 250, absolute 255: accepted, since it equals the maximum and does not exceed it.
Totals: 5 rows, 2 accepted and 3 rejected. The counter ends at 255 after row 5. If you run the rows in sequence from separate starting points, as the fixture states, the counts above still hold, because each row lists its own previous value.
At the limit, accepting row 5 means the counter cannot grow further. The next positive delta will be rejected, and that is the intended signal to enlarge the storage or change the design deliberately.
Stop conditions
- Stop the import when rejected rows exceed a share you set in advance, since this suggests a format or source change.
- Stop and alert when any counter reaches a chosen fraction of its maximum, so people can act before rejection begins.
- Never clamp silently to the maximum, since this hides lost counts.
Proposed acceptance tests (not performed)
None of these has been run.
- Run every fixture row and compare the outcome with expected_outcome.
- Test the exact boundary values: maximum minus one, maximum, and maximum plus one.
- Confirm a rejected row leaves the stored counter unchanged.
- Confirm the rejection list keeps the raw text and the reason.
- Import the same file twice and check that a delta import does not double count. This needs a source identity for each batch, because the overflow check alone does not prevent it.
- Confirm that the accepted plus rejected counts equal the rows read.
These checks reduce the chance of silent corruption in the situations the fixture covers. They do not cover cases you have not written down.