A lost acknowledgement exercise asks which rows change when the consumer commits but the relay cannot mark the outbox delivered.
Spring exercise: predict outbox state across a lost acknowledgement
Run the sequence
Accept EVENT-31 for two units of SKU-17. Let worker A claim it and call the consumer successfully. Stock moves from 10 to 8, and applied_event contains EVENT-31. Stop before acknowledgement. At the lease expiry, worker B reclaims the PENDING row and calls the consumer with the same event ID. Predict the second stock balance, applied_event count, and whether B may acknowledge.
The answer is 8, one, and yes if B still owns an unexpired claim. The second consumer call hits the unique event-ID key and skips the stock update. Worker A must not acknowledge after B has taken the claim. The event ledger and fencing token protect different rows.
Change one assumption
Remove the applied_event unique key. The second call may subtract stock again. Move the applied_event insert into a separate committed transaction before the stock mutation. A business failure can then make a later valid retry look like a duplicate. Predict the final rows before running the checked tests.
Extend the kit with a test for a failed external publish after claim, then state what cannot be proved without a broker. A passing H2 test is evidence for local SQL state transitions only.
Checked source
mvn -q -Dtest=OutboxRelayFlowTest testVerification boundary
OutboxRelayFlowTest has seven local tests in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.
Costs and limits
This is a self-check backed by an in-process source kit, not a hosted judge. The lost-acknowledgement scenario is reasoned from separately checked claims and duplicate calls; no process is actually killed.
Common Mistakes
- Do not change the event ID when replaying the same event.
- Do not assume a failed acknowledgement means the consumer rolled back.
- Do not equate a passed local exercise with target-system recovery.
Read next
Spring relay failure trace: inspect persisted state at each cut point, Spring consumer idempotency: reserve an event ID with the stock mutation, Spring outbox claims: lease expiry and token-fenced acknowledgement, Spring outbox transaction boundaries: where atomicity ends.
