Background jobs

Background work without a worker fleet

Run the long jobs as durable functions that resume after a failure, put them on a cron schedule, and take a lock when only one run may work at a time.

schedulers
volcano cloud functions schedulers create reap-sessions \  --name every-15m --cron "*/15 * * * *"✓ every-15m · */15 * * * * · enabledvolcano cloud durable schedulers create nightly-sync \  --name nightly --cron "0 2 * * *"✓ nightly · each tick starts one durable execution# no worker fleet to keep alive between runs
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, and gives you the pieces that make the rest safe. A durable function records each step as it finishes, so a run that dies resumes where it stopped and a retry never repeats a finished step. A project lock lets exactly one run hold the work at a time. When the work is a queue of rows, claim them in Postgres with FOR UPDATE SKIP LOCKED.

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. Standard and durable functions both take schedules, and there is no scheduler service to operate.

  • 5-field UTC cron, per function, named
  • Enable or disable without deleting
  • An optional JSON payload per schedule
volcano.dev/dashboard/edge-function/schedulers
Schedulers with their cron expression, region and next run
Durability

Jobs that resume instead of restarting

Run a long job as a durable function and each page, batch or API call becomes a step. A failed step retries with backoff on its own, a crash resumes at the first unfinished step, and a finished step never runs again. One execution can take hours, or wait for days, and still pick up where it left off.

  • Retries with backoff, per step
  • atMostOnce with retry: false for one-time emails
  • Up to a year per execution
nightly-sync, 48 pages from a partner API
step  fetch-1 … store-30    succeededstep  fetch-31              failed · 502 from partnerstep  fetch-31              attempt 2 · succeededstep  fetch-32 … store-48   succeeded✓ one execution · pages 1 to 30 were not fetched twice
Safety

One run at a time, when it matters

When two runs must not overlap, such as a rollup or a sync against a rate-limited API, wrap the work in a project lock. One run holds a renewable lease and the others get acquired: false and return. A fencing token lets the protected write refuse a holder that lost its lease.

  • withLock renews the lease while the work runs
  • Fencing tokens so a displaced holder's writes fail
  • FOR UPDATE SKIP LOCKED for queues of rows
two runs, one lock
# both wake at 02:30run A  withLock('nightly-rollup')  acquired · lease 60s, renewingrun B  withLock('nightly-rollup')  acquired: false · skippedno overlapping work, no coordination service
Path to production

How it works

  1. 01

    Pick the shape

    A short batch is a standard function. Anything with several steps, retries or waits is a durable function.

  2. 02

    Write the work

    In a durable function, make each page, batch or call a step. In a standard one, take a bounded batch and return.

  3. 03

    Attach the schedule

    Declare it in the manifest or create it with the CLI. Each tick invokes the function or starts an execution.

  4. 04

    Watch and adjust

    List executions, follow runtime logs, and disable a schedule without deleting it while you investigate.

Code

The long job, the locked job, the schedule, the operating

JavaScript
const { durable } = require('@volcano.dev/sdk/durable'); // Started by a cron tick. Each page from the partner API is its own step, so // a failure on page 31 retries page 31 rather than the whole sync. exports.handler = durable(async (input, ctx) => { let cursor = null; let pages = 0; do { const page = await ctx.step(`fetch-${pages + 1}`, () => fetchPartnerPage(cursor), { retry: { attempts: 5, initialDelay: '10s', maxDelay: '5m' }, }); await ctx.step(`store-${pages + 1}`, () => upsertRows(page.rows)); cursor = page.next_cursor; pages += 1; } while (cursor); // Checkpointed before it runs and never retried, so the summary goes once. await ctx.step('notify', () => sendSummary(pages), { atMostOnce: true, retry: false }); return { pages }; });
Return

What you avoid running

0workers kept warm

A tick invokes the function or starts an execution, and nothing runs in between.

366 daysfor one durable job

An execution can wait, retry and resume across that whole span without holding a runtime.

1 callto hold a lock

withLock takes the lease, renews it and releases it, so overlapping runs skip instead of colliding.

What you get

No overlapping work

A durable replay skips finished steps, and a locked job never overlaps itself.

Failures that resume

A failed step retries on its own, and a crash picks up at the next step.

Visible state

Executions, schedules and logs, from the CLI or the dashboard.

Platform

Built on Volcano

Functions and agents
  • The short jobs

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

Durable functions
  • The long ones

    Durable functions that checkpoint each step, retry what fails and wait without holding a runtime.

Databases and vector
  • The queue

    Postgres holding job state, claimed with FOR UPDATE SKIP LOCKED when a queue of rows is the right shape.

Realtime
  • Telling the UI

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

Frequently asked questions

Read the docs
How do I schedule a function?

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 * * * *". A durable function takes a schedule through volcano cloud durable schedulers create. All of them use 5-field UTC cron.

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. Durable schedules have the same commands under volcano cloud durable schedulers.

What stops two runs doing the same work?

For a job that must not overlap, a project lock: volcano.locks.withLock gives the lease to one run, and the others get acquired: false and return. For a queue of rows, claim them with FOR UPDATE SKIP LOCKED and a time limit on each claim, so a second run picks up different rows and a run that dies gives its rows back.

How long can a job run?

A standard function gets up to 30 seconds per invocation on Hobby or 180 seconds on Superagent, so it should process a batch and leave the rest for the next tick. A durable-function step gets up to 300 seconds on Hobby or 900 seconds on Superagent, and one execution can run for up to 366 days.

Are jobs retried automatically?

In a durable function, yes: a failed step retries with exponential backoff, and you set the attempts and delays per step. A standard function is not retried for you, so keep an attempts count on the row, back off with run_after, and mark the job failed once it exceeds its limit.

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