A poller batch limit bounds the rows one callback will claim before it yields to the scheduler.
Spring outbox polling: cap work per tick and measure backlog age
A bounded selection
The fixture inserts three due event rows and invokes runBatch(2). Two rows become DELIVERED and one remains PENDING. A second call handles the last row. The selection orders by event ID and applies LIMIT 2, then each candidate is conditionally claimed before the fake transport runs. The claim statement repeats the due and lease predicates because another worker could change a row after the SELECT.
The source kit does not run this test concurrently. A target database may use different locking and query plans. Add an index for state and due time only after measuring representative data, then check fairness when one old event repeatedly fails. A retry due time keeps a failed row from monopolizing every tick.
Choose a budget from resources
A batch of two is a small test value, not a recommended production setting. Each callback borrows database connections, allocates rows and may hold a scheduler thread while the transport waits. Cap each remote call and the whole tick. If one event stalls, a fixed-delay schedule pauses subsequent ticks on that thread.
Backlog count alone misses aging work. Track the oldest due event, claim latency, delivery latency and dead-row count with bounded metric labels. The measurement lesson separates dimensions suitable for metrics from event IDs that belong in diagnostic logs.
Checked source
select event_id from scheduled_outbox
where state = 'PENDING' and next_due <= ?
and (claim_token is null or lease_until <= ?)
order by event_id limit ?;Verification boundary
ScheduledOutboxPollerTest.onePollProcessesOnlyItsBatchBudget. The excerpt is shortened or a labelled design sketch; the source kit contains the complete checked fixture.
Costs and limits
The selection and claim run on H2 with three rows. No production index plan, contention measurement, fairness proof, or multi-node batch benchmark is included.
Common Mistakes
- Do not select every pending row into memory.
- Do not trust a prior SELECT as ownership.
- Do not size a production batch from a three-row test.
Read next
Spring TaskScheduler: poll committed outbox rows in the background, Spring scheduled retries: leave failed work pending until its due time, Spring relay metrics: measure backlog without event-ID labels, Spring outbox claims: lease expiry and token-fenced acknowledgement.
