Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring scheduled relay: recover when the consumer commits before acknowledgement

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A relay can lose its acknowledgement after the consumer commits, leaving a PENDING event that will be delivered again.

Download Spring source kit

The exact cut point

The test inserts EVENT-61 for four units, calls the local consumer directly and deliberately leaves the outbox row PENDING. The consumer commits an applied_event key and stock changes from 10 to 6. The poller then selects the event, invokes the same consumer again and acknowledges the outbox row. The second consumer invocation hits the unique event key and does not subtract stock again. Final state is DELIVERED, one applied marker, stock 6.

This is a controlled cut point, not a process crash. It proves the local duplicate path once the event marker is already present. The consumer transaction is the guard. The lease token only controls who may update the outbox row; it cannot undo a consumer commit.

Treat uncertain acknowledgements as ordinary

A real broker publish may succeed even when its acknowledgement is lost. If a relay assumes the timeout means no delivery, it may send the same event again. The event ID should survive retries, and the consumer should store enough state to return a safe duplicate result.

The fixture stores both outbox and consumer tables in one H2 database for convenience, though it calls separate transactions. A real consumer can use another database. Integration tests must kill the relay between consumer commit and outbox acknowledgement and verify recovery after restart.

Checked source

Java
consumeLocally("EVENT-61"); // Consumer commits; outbox remains PENDING.
new Poller(this::consumeLocally, () -> {}).runBatch(1);
assertEquals(6, jdbc.queryForObject(
    "select available from stock_balance where sku = 'SKU-29'", Integer.class));

Verification boundary

ScheduledOutboxPollerTest.redeliveryAfterLostAcknowledgementDoesNotRepeatStockMutation. The excerpt is shortened or a labelled design sketch; the source kit contains the complete checked fixture.

Costs and limits

The test invokes the consumer twice in one process. It has no broker offset, process kill, network partition, separate consumer database or deduplication retention policy.

Common Mistakes

  • Do not equate a lost acknowledgement with a rolled-back consumer.
  • Do not reserve an event marker outside the business transaction.
  • Do not discard a stable event ID when retrying.

Read next

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, Spring relay integration tests: prove the boundaries H2 cannot.

spring
spring-boot
scheduled-relay
Storage details