Skip to content

Functions

Writing, deploying and operating persistent workers.

View as Markdown

Write a worker

On the default runtime, export an async default function. In every other runtime, write the long-running process your language would write anyway. Whichever it is, handle errors inside your loop and include a delay, otherwise the worker will consume a full CPU doing nothing useful.

typescript
export default async function main() {
  while (true) {
    try {
      console.log('Worker heartbeat', new Date().toISOString());
    } catch (error) {
      console.error(error);
    }
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
}

Deploy

A worker is not bound to JavaScript. With the default runtime the CLI bundles local imports with esbuild from a single entry file; with Python, Go, .NET, Rust, Java, Bun, Deno or Docker it packages the directory and builds it on the server, exactly as it does for an application. Secrets are passed as environment variables either way.

bash
veltic functions deploy PROJECT_ID heartbeat ./worker.ts --memory 256
veltic functions deploy PROJECT_ID importer ./ --runtime go --memory 512

Process jobs reliably

Store job state in PostgreSQL. Processes restart, so handlers must be idempotent. Use an atomic claim so competing workers never take the same row.

sql
update jobs
set status = 'running', claimed_at = now()
where id = (
  select id from jobs
  where status = 'pending'
  order by created_at
  for update skip locked
  limit 1
)
returning *;

When a worker dies

Exceeding the hard memory limit terminates the container through the kernel; the restart policy retries up to five times. Check the logs and the memory chart in the dashboard rather than assuming a transient fault.