Menu
Platform
AI
App store purchases
Database
Flags
Jobs and cron
Localization
Monitoring
Notifications
Payments
Queues
Sandboxes
Webhooks
Getting Started
Authentication
KV Store
Deploy & Infrastructure
Reference
Schedules
How a Schedule fires, what a fire records, and the fences that keep a delete honest.
Two shapes describe this product, and it is worth knowing which one a fact
comes from. The declared schedules collection is the Schedule Resource the
one API documents — the name pattern, the spec, pause and resume, written
down in the collection. Schedules run on the Compute
API today, whose wire is camel case (identity.revision, everySeconds,
catchUp).
#One instant, one Run
(schedule, intended time) is the Run identity. A fire names the instant it
was due for, and one instant starts at most one Run — a wake that arrives
twice, as a retry or a duplicate, starts the same instant's Run once. That is
what makes a schedule safe to run on infrastructure that can deliver a wake
more than once: the duplicate has nowhere to land.
A fire is not a Run result. The fire starts the Run; whether the Run succeeded, failed, or is still going is the Run's own outcome, read from the Run rather than from the schedule's fire. The two are separate facts because they answer different questions — the fire says the work was started on time, and the Run says what the work did.
#What a fire records
A fire is recorded as a tick, and a tick holds one of three states:
- open
- The fire has started and nothing has settled yet.
- succeeded
- A 2xx answer came back from the target.
- dead_lettered
- The target refused, so the fire is recorded as a dead letter.
A refusal is recorded, not swallowed: a dead letter sits beside the tick that
produced it, so the schedule's own state carries the evidence of what was
refused instead of a gap where a fire should have been. The schedule read
shows both — lastTick for the most recent fire, deadLetters for the
refusals it collected.
#Overlap: a fire that arrives while the last Run is still going
A rate can come due again while the Run its last fire started is still
running, and overlap is the policy for that moment. The Compute API takes
skip, buffer_one and allow; the declared collection lists a wider set
(skip, buffer_one, buffer_all, cancel_other, allow).
The question the policy answers is about the handler, not the schedule.
allow starts the new Run regardless, which is right for a handler that is
safe to run twice at once. skip starts nothing while the previous Run is
still open, which is the safe default for a handler that is not. buffer_one
holds one fire back rather than dropping it outright.
#Catch-up and the missed-fire window
A fire that nobody delivered — the schedule was paused, or the platform was not there to fire it — is a missed fire, and the catch-up policy decides whether it is still worth starting. Starting an old fire late is only useful inside a window: a window bounds how stale a missed fire may be and still start.
On the declared collection that window is catchup_window, a duration from 10
seconds to 365 days, and a missed fire older than it is skipped. On the
Compute API the field is catchUp, where skip drops the missed fires and
the bounded form states how many, or how old, a missed fire may be and still
start.
Catching up is the expensive direction: a schedule that was down for a while starts every missed fire the window kept, so a window that is larger than the work justifies turns a short outage into a burst.
#Pausing
A Schedule carries pause, and a paused Schedule starts no Run until it is
resumed. The declared collection spells that as pause and resume calls,
with an optional pause_note that says why the Schedule is paused; resume
follows the catch-up window for whatever was missed while it was paused, which
is the same rule that governs a missed fire any other way.
Pausing is a state of the schedule rather than a change to it, which is what makes it the right tool for a stop that is meant to end: a paused schedule keeps its calendar, its spec and its revisions, and nothing else about it changes while it waits.
#Revisions, and the delete fence
A schedule is edited in place, and every change moves its
identity.revision. A delete states which revision it is deleting —
expectedRevision, taken from the last read — and that is a fence rather than
a formality: a tick between your read and your delete moves the revision, and
the delete is refused with a conflict instead of removing a schedule you did
not read. Read it again, and delete with the revision you just read.
The same call carries an idempotencyKey, so a delete that is retried is one
delete and not two.