Skip to content
Console
Menu

Queues

Workflows

Getting Started

Authentication

KV Store

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.