Cron Scheduling
Note
This feature requires a Zizq pro license on the server.
Zizq supports running jobs periodically, using a cron scheduling system that is managed atomically by the Zizq server. Multiple schedules can be defined, and any number of entries can exist on each schedule. Both schedules and individual entries within a schedule can be paused and resumed. Explicit time zones are supported.
The official Zizq Node client exposes this functionality through
client.cron().
Defining a Schedule
Schedules are defined by your application, not by configuration files on the
Zizq server. Your application includes a client.cron("name").register({...})
call somewhere in its startup code.
Schedule registrations are idempotent. There is no requirement to designate one particular process as the process that defines the schedule. If you have 50 application processes and they all define the same schedule on startup, that is fine and expected.
Schedule defintions are also mutable. If you change a schedule between application versions, Zizq is smart enough to retain existing entries, replace modified entries, and delete removed entries.
The register() method takes an array of entries, each of which defines a cron
expression (and optionally a timezone) and details of the job to be executed at
that time. Each entry is assigned a name known to your application, which
uniquely identifies that entry within the schedule.
Both 6-field (with seconds) and standard 5-field cron expressions are accepted.
JS:
import { Client } from "@zizq-labs/zizq"; import { sendDailyDigest } from "./handlers"; const client = new Client({url: "http://localhost:7890"}); await client.cron("my-cron").register({ timezone: "Europe/London", entries: [ { name: "refresh_data_warehouse", expression: "*/15 * * * *", type: "refresh_data_warehouse", queue: "data_warehouse", payload: { incremental: true }, }, { name: "send_daily_digest", expression: "0 9 * * *", type: sendDailyDigest, // function handlers are supported payload: {}, }, { name: "rotate_logs", expression: "0 0 * * *", timezone: "UTC", type: "rotate_logs", queue: "system/maintenance", priority: 100, payload: {}, } ], });
Batching scheduled jobs
An entry may carry batch, so a schedule that fires while a previous job of
the same batch is still pending folds into it rather than creating a second
one. See Batched Jobs.
import { batchConfig } from "@zizq-labs/zizq"; await client.cron("my-cron").register({ entries: [ { name: "flush_audit_events", expression: "*/5 * * * *", type: "audit.events", queue: "audit", payload: { events: [] }, batch: batchConfig(1000, ".events"), }, ], });
Throttling scheduled jobs
An entry may carry budgets, so a scheduled job is throttled exactly as an
enqueued one is — it fires on its expression, then waits on the server until
its budgets can afford it. See
Concurrency & Rate Limiting.
await client.cron("my-cron").register({ entries: [ { name: "send_daily_digest", expression: "0 9 * * *", type: "send_daily_digest", queue: "emails", payload: {}, budgets: [{ key: "emails", cost: 5 }], }, ], });
Timezones
The timezone passed to register() applies to every entry that does not
specify one of its own, so a schedule that runs in a single timezone says so
once. An entry with its own timezone overrides it, which is how one schedule
can hold entries in several zones.
With neither set, expressions are evaluated in the system timezone of the Zizq server.
The schedule’s timezone is stored on the server as the schedule’s own rather than being copied onto each entry, so it is still there when the schedule is read back:
JS:
const schedule = await client.cron("my-cron").get(); schedule.timezone; // "Europe/London"
Note
A schedule-level timezone requires Zizq 0.7.0 or newer on the server. Against an older server it is ignored, and entries relying on it fall back to the server’s system timezone.
The Zizq server will push jobs to the queue and advance the schedule atomically. There is no risk that a job will be enqueued twice for the same schedule tick. However, because Zizq simply enqueues the job without waiting for completion, you could end up with overlapping jobs if they are scheduled too close together and take a long time to run. If this is a concern for your application, you should combine cron scheduling with Unique Jobs in order to prevent duplicate enqueues.
Inspecting Existing Schedules
Once defined, cron schedules can be fetched and inspected by your application
by using client.cron("name").get(). Some management operations such as
pause/resume are also available on the returned schedule.
Note
client.cron()is lazy. Requests to fetch the schedule data are only sent to Zizq server when one of the access methods (e.g.get(),pause(),resume()is called on it.
JS:
const schedule = await client.cron("my-cron").get(); schedule.paused; // false for (const entry of schedule.entries) { console.log(`${entry.name}: ${entry.expression} (${entry.job.type}) last enqueued ${entry.lastEnqueueAt}`); }
Pausing & Resuming Schedules
Schedules can be paused and resumed at two distinct levels:
- At the outer cron group/schedule level.
- Per-entry within the schedule.
If the group itself is paused, no entries within that schedule will execute even if they are not paused.
JS:
// Pause/resume entire schedule let schedule = await client.cron("my-cron").pause(); console.log(schedule.paused); // true console.log(schedule.pausedAt); // ~ now schedule = await client.cron("my-cron").resume(); console.log(schedule.paused); // false console.log(schedule.pausedAt); // ~ before console.log(schedule.resumedAt); // ~ now // Pause/resume individual entry let entry = await client.cron("my-cron").entry("refresh_data_warehouse").get(); console.log(entry.paused); // false entry = await client.cron("my-cron").entry("refresh_data_warehouse").pause(); console.log(entry.paused); // true console.log(entry.pausedAt); // ~ now entry = await entry.resume(); console.log(entry.paused); // false console.log(entry.pausedAt); // ~ before console.log(entry.resumedAt); // ~ now
When paused, Zizq stops enqeueing jobs but continues to advance the schedule.
It is also possible for the paused state to be specified within the schedule definition itself, which is useful e.g. if schedule pausing needs to be coupled to app deployments (e.g. as part of a phased rollout of a complex change).
JS:
await client.cron("my-cron").register({ paused: true, ... });
Tip
Each entry in the array also accepts
paused: trueorpaused: false.
Deleting a Schedule
Entire cron group schedules can be deleted, along with all of their entries by
calling delete() on the handle.
JS:
await client.cron("my-cron").delete();
Adding, Replacing or Deleting Entries within a Schedule
In general you should just define your schedule(s) in your application startup
code using client.cron("...").register({...}) and let Zizq keep that schedule
in sync. However it is also possible to add/replace entries dynamically by
calling entry("name").register({...}) directly on the cron handle.
JS:
await client.cron("my-cron").entry("example").register({ expression: "* * * * ", queue: "example-queue", type: "example_job", payload: {}, });
If "example" already exists on this schedule, it is replaced with the new
entry, otherwise the new entry is appended to the schedule.
Call delete() on an entry to remove it from the schedule.
JS:
await client.cron("my-cron").entry("example").delete();