Skip to content

Delayed and repeatable jobs

You’ve got immediate jobs and retries working. Now schedule a job to run in the future, then upgrade it to a cron spec that fires every day at 09:00.

Pass delay (milliseconds) on Queue.add and the job lands in a Redis sorted set instead of the stream. The promoter (embedded in your worker process) moves it into the stream when its run-at time arrives.

import { Queue, Worker } from "chasquimq";
const connection = { host: "127.0.0.1", port: 6379 };
const queue = new Queue("reminders", { connection });
new Worker("reminders", async (job) => {
console.log(`firing ${job.name} at ${new Date().toISOString()}`);
}, { connection });
// Fire 5 seconds from now.
await queue.add("ping", { user: 42 }, { delay: 5_000 });

Five seconds in: one log line. The job sat in {chasqui:reminders}:delayed until the promoter swept it into the stream.

  • Queue.add(name, data, { delay: ms }) writes to {chasqui:reminders}:delayed (a sorted set scored by run-at-ms).
  • A promoter task — by default embedded in every worker, with SET NX EX leader election so only one fires per tick — runs ZRANGEBYSCORE for due entries and atomically promotes them into the main stream.
  • The first available worker reads the entry and runs your handler.

max_delay_secs on the producer caps how far in the future a job may be scheduled (default 30 days). Set it to 0 to disable the cap.

Repeatable jobs upsert a spec — the engine then mints a fresh ULID for each fire.

await queue.add(
"daily-rollup",
{ region: "eu-west" },
{
repeat: {
pattern: "0 9 * * *",
tz: "Europe/Madrid",
},
},
);

Cron timezones accept IANA names (Europe/Madrid, America/New_York) and are DST-aware: 0 2 * * * fires at the local 02:00 wall-clock on both sides of spring-forward / fall-back.

For fixed intervals instead of cron, use every:

await queue.add("ping", { user: 42 }, { repeat: { every: 60_000 } });

First fire lands one interval after upsert. There is no immediate fire on every.

Terminal window
chasqui repeatable list reminders

Renders a table with each spec’s resolved key, dispatch name, pattern, and next fire time.

To remove:

const specs = await queue.getRepeatableJobs();
for (const s of specs) {
if (s.jobName === "daily-rollup") {
await queue.removeRepeatableByKey(s.key);
}
}

Or from the CLI:

Terminal window
chasqui repeatable remove reminders 'daily-rollup::cron:0 9 * * *:Europe/Madrid'

If your worker process restarted and missed one or more cron fires, the engine’s MissedFiresPolicy decides what to do:

  • skip (default) — drop missed windows. No thundering herd after a deploy.
  • fire-once — emit a single job to represent the missed window, then advance.
  • fire-all { maxCatchup } — replay each missed window, capped at maxCatchup.
await queue.add("daily-rollup", data, {
repeat: {
pattern: "0 9 * * *",
tz: "Europe/Madrid",
missedFires: { kind: "fire-once" },
},
});

See Schedule repeatable jobs for the trade-offs.