Cron runs a task on a recurring schedule: every five minutes, every night at
2am, every Monday morning. It is for the work an app does on a clock rather
than in response to a visitor: refreshing stats and metrics, sending the weekly
newsletter, cleaning up old rows, expiring trials. Think of it as setInterval
for your app, with a schedule written in plain words, and it runs once per tick
however many servers you deploy. Cron comes with Elements, with no scheduler to
set up.
Define cron tasks in index.ts
Cron tasks are defined in your app's index.ts, next to your routes, with
app.cron(). Each one is a line: when it runs, a name for the logs, and the
function to call.
index.tsapp.cron("every 5m", "refresh stats", () => refreshStats()); app.cron("every day at 2am", "archive old rows", () => archiveOldRows()); app.cron("every monday at 9am", "weekly newsletter", () => new SendNewsletterJob().schedule());
The function is ordinary server code, so it can call sql(), read config or
call any of your helpers:
app/lib/archive.tsimport { sql } from "@elements/app"; export function archiveOldRows() { sql(` update messages set archived = true where createdAt < now() - interval '90 days' `); }
Cron tasks run on the app server
A cron task runs inside your app's process, the same process that serves your
pages, the way a setInterval would. Every minute the app checks its
schedules and runs each task that is due, one after another.
So expensive work does not belong in a cron task. While it runs:
- It takes CPU from your app server. Expensive work in a cron task uses the same process and CPU that serve your pages.
- Other cron tasks wait. Tasks that are due run one after another, so a long one delays the rest.
- A failure is not retried. A cron task has no queue, no retries and no timeout. If it throws or is cut off partway, that run is simply over until the next tick.
Keep a cron task to quick work, like one query that updates a set of rows. Put expensive work in a job: the cron task schedules it, as the weekly newsletter above does, and returns at once. A separate worker process does the work, with retries, and the app server keeps serving pages.
How several app servers coordinate
Deploy your app to three servers and all three run the same cron schedules, but each tick runs on exactly one of them. Every minute, each server tries to claim that minute in a one-row table in your Postgres database. Postgres lets exactly one claim succeed; that server runs the tasks due that minute, and the others do nothing until the next one. So the 2am archive runs once a night, not three times, with no coordinator to run and no setting to change as you add servers.
Every run is recorded
Each run is saved in your database with its name, when it ran, how long it took and any error. Read the latest runs at any time:
Terminalelements db -sql "select * from elements.cron_runs order by ran_at desc limit 20"
See cron in a demo
- Dealwren (opens in a new tab), a CRM that emails each rep their tasks every morning.
The manual: jobs (opens in a new tab), database (opens in a new tab).