Background jobs

Cron for functions you already deployed

Attach a schedule to any function from the manifest or the CLI, and keep the work queue in the database you already have.

schedulers
volcano cloud functions schedulers create nightly-rollup \  --name rollup --cron "0 2 * * *"✓ rollup · 0 2 * * * · enabledvolcano cloud functions schedulers list nightly-rolluprollup   0 2 * * *    enabled   us-east-1# no worker fleet to keep alive between invocations
Overview

The scheduler is not the hard part

Firing a job on a cron expression is easy. What bites is everything after: two instances waking at once and doing the same work twice, a run that dies halfway and leaves rows half-updated, and a retry that sends the same email a second time.

Volcano runs the trigger, so there is no box to keep alive. You keep the state in Postgres: a run claims the rows it is about to process for a few minutes — a lease — does the work, then marks them done. A second run finds nothing to claim, and a run that dies gives its rows back when the lease expires. That is what makes a job safe to run twice and safe to resume.

Triggers

A schedule is part of the function

Declare it in volcano-config.yaml so it is reviewed with the code, or create it from the CLI when you need one now. Either way there is no separate scheduler service to operate.

  • 5-field cron, per function, named
  • Enable or disable without deleting
  • An optional JSON payload per schedule
volcano cloud functions schedulers list
rollup        0 2 * * *      enabledsend-digest   0 9 * * 1      enabledreap-sessions */15 * * * *   enabledbackfill      0 * * * *      disabled# disabled stays defined, just does not fire
Safety

Claim the work, do not just start it

Mark the rows you are about to process as yours for the next few minutes. A second run finds nothing left to claim, and a run that dies gives its rows back when that window expires, rather than blocking the queue forever.

  • FOR UPDATE SKIP LOCKED to claim a batch
  • A claim that expires if the run dies
  • Attempt counts so failures stop eventually
two runs, one batch
# both wake at 02:00run A  claimed 50 jobs  (lease 5m)run B  claimed 0 jobs   (nothing left to claim)no duplicated work, no coordination service
Shape

Batches, not daemons

An invocation is capped at 180 seconds, so a job should take a bite and leave the rest for the next run. That constraint is also what makes the work restartable.

  • Process a bounded batch each run
  • Progress is persisted, so a crash resumes
  • Frequent small runs beat one long one
volcano.dev/dashboard/edge-function
Function runs over time, each one a short batch
Path to production

How it works

  1. 01

    Queue it in Postgres

    A jobs table with status, attempts and a lease column. No extra broker to run.

  2. 02

    Write the worker

    Claim a batch, process it, record the outcome, and return.

  3. 03

    Attach the schedule

    Declare it in the manifest or create it with the CLI, then enable it.

  4. 04

    Watch and adjust

    Follow runtime logs, and disable a schedule without deleting it while you investigate.

Code

The queue, the worker, the schedule, the operating

SQL
create table if not exists jobs ( id uuid primary key default gen_random_uuid(), kind text not null, payload jsonb not null default '{}'::jsonb, status text not null default 'pending' check (status in ('pending', 'running', 'done', 'failed')), attempts integer not null default 0, max_attempts integer not null default 5, leased_until timestamptz, last_error text, run_after timestamptz not null default now(), created_at timestamptz not null default now() ); -- Claimable work: pending, due, and not currently leased by another run. create index if not exists jobs_claimable_idx on jobs (kind, run_after) where status = 'pending'; -- One job per (kind, key) when a caller must not enqueue twice. create unique index if not exists jobs_dedupe_idx on jobs (kind, (payload->>'key')) where status in ('pending', 'running');
Return

What you avoid running

0workers kept warm

Functions run on the schedule and nothing is running in between.

1 tableinstead of a broker

The queue is Postgres, so job state is visible to the same SQL as everything else.

5-fieldcron per function

Named schedules you can enable or disable without deleting or redeploying.

What you get

No duplicated work

A run that overlaps another finds nothing left to claim.

Failures that resume

A dead run gives its rows back when its claim expires.

Visible state

Attempts, errors and progress are rows you can query.

Platform

Built on Volcano

Functions and agents
  • The jobs themselves

    Functions with named cron schedules, declared in the manifest or created from the CLI.

Databases and vector
  • The queue

    Postgres holding job state, with time-limited row claims instead of a message broker to run.

Realtime
  • Telling the UI

    A job's write reaches subscribers over the change stream, with nothing to publish.

File storage
  • Files a job produces

    Exports and reports land in a bucket where policies decide who can download them.

Frequently asked questions

Read the docs
How do I schedule a function?

Either declare a schedulers block on the function in volcano-config.yaml and deploy, or run volcano cloud functions schedulers create <function> --name <name> --cron "*/5 * * * *". Both take a 5-field cron expression.

Can I pause a job without deleting it?

Yes. volcano cloud functions schedulers list <function> prints the scheduler id, and disable <function> <scheduler-id> stops it firing while keeping the definition. enable turns it back on.

What stops two runs doing the same work?

Claim rows with FOR UPDATE SKIP LOCKED and put a time limit on each claim. A second run picks up different rows or none at all, and a run that dies gives its rows back when the limit expires.

How long can a job run?

One invocation is capped at 180 seconds. Process a batch per run and let the next tick continue, rather than trying to finish everything at once — the per-plan ceiling is on the pricing page.

Are jobs retried automatically?

Volcano does not retry them for you. Keep an attempts count on the row, back off with run_after, and mark the job failed once it exceeds max_attempts.

Which region does a schedule run in?

One region, defaulting to a region the function is deployed in. You can pin it with --regions when you create the scheduler.

Ready to schedule your first job?

Build, deploy, and scale on Volcano's global platform — free to start, with no infrastructure to manage.

Explore more solutions