Outbox implementation notes
This document describes the bounded retries, exponential backoff, and dead-letter handling implemented in the event_outbox subsystem.
Schema changes
next_attempt_at TIMESTAMPTZ: newly added column used to schedule the earliest time an event is eligible for retry.statusallowed values now includedead_letterto represent a terminal, non-retriable state.
Behavior
- On publish failure the repository's
markFailed()incrementsretry_count, records theerror_message, clears any consumer/lease fields, and setsnext_attempt_atusing an exponential backoff formula:NOW() + 2^(retry_count + 1) seconds. - When
retry_count + 1 >= max_retriesthe event transitions todead_letterandprocessed_atis set to the current time. claimEvents()selects only events whosenext_attempt_atis NULL or in the past, preserving ordering among selected events bycreated_at.
Operational notes
- Dead-lettered events are counted by the
outbox_dead_letter_total{error_code}Prometheus counter (ifprom-clientis available). This helps alert on sustained failures. - Reprocessing or manual inspection can be done by querying rows with
status = 'dead_letter'. - Cleanup policies still apply — retention configuration controls when published/failed/dead-letter events are removed.
Migration
- A migration
007_outbox_bounded_retries.tsadds thenext_attempt_atcolumn, updates thestatuscheck constraint to includedead_letter, and creates an index onnext_attempt_atfor efficient selection of due events.
Testing
- Unit tests cover exact-at-max transitions, due/not-due selection, and ordering preservation when older events are backed off.