I run batches of jobs that call a paid, occasionally-flaky external API. When that API goes down, the current failure shape is the worst one: every job in the fleet burns its full retry budget — max_attempts with backoff, per job, spread across workers — and then the batch fails anyway. So I pay for N × max_attempts doomed API calls and the batch takes the longest possible time to arrive at the outcome the first handful of failures already predicted.
Retry is strictly per-job, and the batch primitives (BatchHandle.status, wait_for_batch, BatchCompletionStatus) are read-only aggregation. There's no shared "if K jobs in this batch/queue have failed consecutively, stop dispensing and fail the rest" — no counter keyed by batch or queue that the dispatch path consults, no abort-the-remainder primitive.
Today I approximate it from a supervisor process: poll BatchHandle.status, and once failed crosses a threshold, paginate through the remainder and cancel one job at a time. Jobs keep dispatching between polls, and the abort decision lives outside the fleet, so every app that needs this re-implements it (slightly differently, slightly wrong).
What I'd rather declare, roughly:
await client.enqueue_batch(
items,
failure_policy=AbortBatchAfter(consecutive_failures=5),
)
or the same idea at the actor_config/queue level — a shared consecutive-failure counter (the leader loop or the dispatch transaction feels like the natural home) that, past the threshold, moves the batch's remaining pending jobs to a terminal state without dispatching them. Snooze/RetryAfter already give per-job control flow; this would be the batch-level analogue.
If this is achievable today through some hook I've overlooked — the reclaim event cursor, a leader-side extension point — I'd love a pointer. Otherwise: does a batch/queue failure policy belong in TaskQ, or do you see this as firmly application-side orchestration?
I run batches of jobs that call a paid, occasionally-flaky external API. When that API goes down, the current failure shape is the worst one: every job in the fleet burns its full retry budget —
max_attemptswith backoff, per job, spread across workers — and then the batch fails anyway. So I pay for N × max_attempts doomed API calls and the batch takes the longest possible time to arrive at the outcome the first handful of failures already predicted.Retry is strictly per-job, and the batch primitives (
BatchHandle.status,wait_for_batch,BatchCompletionStatus) are read-only aggregation. There's no shared "if K jobs in this batch/queue have failed consecutively, stop dispensing and fail the rest" — no counter keyed by batch or queue that the dispatch path consults, no abort-the-remainder primitive.Today I approximate it from a supervisor process: poll
BatchHandle.status, and oncefailedcrosses a threshold, paginate through the remainder and cancel one job at a time. Jobs keep dispatching between polls, and the abort decision lives outside the fleet, so every app that needs this re-implements it (slightly differently, slightly wrong).What I'd rather declare, roughly:
or the same idea at the
actor_config/queue level — a shared consecutive-failure counter (the leader loop or the dispatch transaction feels like the natural home) that, past the threshold, moves the batch's remaining pending jobs to a terminal state without dispatching them.Snooze/RetryAfteralready give per-job control flow; this would be the batch-level analogue.If this is achievable today through some hook I've overlooked — the reclaim event cursor, a leader-side extension point — I'd love a pointer. Otherwise: does a batch/queue failure policy belong in TaskQ, or do you see this as firmly application-side orchestration?