Background Jobs

This page covers the internal implementation of the worker system. For adding custom jobs, see Adding Jobs.

Job Storage

Jobs are stored in the workerJobs table with these key fields:

FieldPurpose
tagIdentifier for logging and debugging
typeJob type name matching the registry
userIdAssociated user (0 for system jobs)
dataJSON payload passed to the job runner
dueWhen the job should run
startedWhen execution began
finishedWhen execution completed
successWhether the job succeeded
resultReturn value from the job runner
cronScheduleCron expression for recurring jobs
persistentWhether job survives server restarts
autoRescheduleOnFailureRetry on failure
autoRescheduleOnFailureDelaySeconds to wait before retry
removeDelaySeconds to keep completed job

Worker Process

Each worker runs as a forked child process with its own ServerApp instance. The start-worker.ts entry point handles:

  1. Creating a ServerApp without the WorkerManager (to avoid recursive spawning)
  2. Connecting to the database with a smaller connection pool
  3. Starting the job polling loop
  4. Graceful shutdown on SIGTERM

Workers poll the database for jobs where due <= now and started IS NULL, using the getNextPendingJob() function to claim jobs atomically.

IPC Communication

Workers communicate with the main server through Node.js IPC. Message types are defined in ipc-types.ts:

  • WorkerStarted — Worker process initialized
  • WorkerShutdown — Worker shutting down
  • WorkerError — Unhandled error in worker
  • Heartbeat — Keep-alive signal
  • SendToUser — Route WebSocket message to specific user
  • Broadcast — Send WebSocket message to all connected clients

When a job needs to send a real-time notification, it uses serverApp.createNotification() which saves to the database and sends an IPC message. The main server receives this and routes it through the SocketManager.

Cron Scheduling

Jobs with a cronSchedule field automatically reschedule after completion. The schedule uses standard cron syntax:

┌───────────── second (0-59)
│ ┌───────────── minute (0-59)
│ │ ┌───────────── hour (0-23)
│ │ │ ┌───────────── day of month (1-31)
│ │ │ │ ┌───────────── month (1-12)
│ │ │ │ │ ┌───────────── day of week (0-6)
│ │ │ │ │ │
* * * * * *

The removeOldWorkerJobs job runs hourly to clean up completed job records.

Error Handling

When a job throws an error:

  1. The finished timestamp and success = false are recorded
  2. If autoRescheduleOnFailure is true, a new job is scheduled after the configured delay
  3. The error is logged with the job context

Jobs don't retry infinitely—the rescheduled job is a new record. The original failed job remains for debugging until the cleanup job removes it.

Monitoring

Job execution is logged with structured data including job ID, type, duration, and result. Enable debug logging with WORKER_LOG_LEVEL=debug for detailed execution traces.

The workerJobs table serves as both queue and audit log. Query it to check pending jobs, recent failures, or execution patterns.