execute_at, and the runner picks it up on the next tick.
That guarantee has a sharp edge.
An instance that was down for six hours comes back holding every task that fell due in those six hours - and without a bound, it fires all of them at once, at your own API.
Two knobs shape what happens instead.
Bounded concurrency
SCHEDY_MAX_CONCURRENT_DELIVERIES (default 50) caps how many deliveries run at the same time.
A backlog drains steadily rather than arriving as one spike: with the default, 10,000 overdue tasks against a target answering in 200ms drain in about 40 seconds, and the target never sees more than 50 concurrent requests.
Every task is still delivered.
Raise it if your targets are fast and you want a shorter drain; lower it if they are fragile.
Watch schedy_deliveries_inflight against the limit - sitting at the cap means the runner is saturated and lateness is accumulating.
The cap applies at all times, not just after an outage. A thousand tasks scheduled for the same instant have exactly the same shape as a backlog.
Staleness
Some work is worth doing late and some is not. A nightly report can arrive an hour behind; a “your code expires in 5 minutes” webhook six hours late is worse than never sending it.SCHEDY_MAX_STALENESS is unset by default - Schedy catches everything up, however old.
Set it to a Go duration and any task that comes due more than that late is skipped instead of delivered.
failed, with the reason recorded as an attempt:
schedy_tasks_skipped_total.
A skipped task you decide was worth doing after all can be replayed.
Skipping is never silent - an outage that quietly swallowed work would be worse than one that fired it late.
A recurring task that is skipped still re-enqueues, anchored forward from the moment it was skipped. An outage interrupts a chain; it does not end it.
Choosing a value
There is no default that is right for both a nightly report and a 5-minute expiry link, which is why there isn’t one.- Leave it unset if every task is worth doing whenever it happens - billing runs, syncs, cleanup jobs. This is the historical behavior.
- Set it near your longest acceptable delay if tasks are time-sensitive. A value slightly above your longest expected outage plus drain time skips only genuinely dead work.
- Set it low only if you would rather drop than deliver late, and you are watching
schedy_tasks_skipped_total.