Background Jobs Overview
The background jobs subsystem provides a structured way to execute work outside the request/response cycle. Jobs are defined as TypeScript classes, decorated with metadata that describes their scheduling, retry behaviour, priority, and tenant scope. At runtime, a provider queues and executes those jobs, while telemetry and lifecycle hooks keep every execution observable.
Key Concepts
- Job definition — a class decorated with
@AIBackgroundJobthat implements aexecute(context)method. The decorator registers the class in a globalJobRegistryat module load time. - Provider — the queue backend. Two providers ship with UDIOT:
InMemoryJobProviderfor local development and testing, andBullMQJobProviderfor production workloads backed by Redis. - Runtime —
BackgroundJobRuntimeis the engine. It accepts a provider, exposesscheduleJob(), and wraps every execution in an OpenTelemetry trace span. - Scheduler —
JobSchedulerreads cron expressions and interval schedules from the registry and triggers jobs on time by re-enqueueing them via the runtime. - Telemetry — every job execution is wrapped in a
jobs.processspan. Metrics (jobs.started,jobs.failed,jobs.duration_ms,jobs.retried) are emitted through the shared UDIOT metrics registry.
Architecture
┌─────────────────────────────────────────────────────┐
│ @AIBackgroundJob │
│ @AISchedule (optional) │
└──────────────────────┬──────────────────────────────┘
│ registers
▼
┌─────────────────┐
│ JobRegistry │ ← module-level singleton
└────────┬────────┘
│ read by
┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ JobScheduler │ │BackgroundJobRuntime│
│ (cron/interval)│ │ wraps spans + │
└──────┬───────┘ │ metrics │
│ enqueues └────────┬──────────┘
└──────────────────┐ │ calls
▼ ▼
┌────────────────┐
│ JobProvider │
│ ┌──────────┐ │
│ │InMemory │ │ ← dev / test
│ │BullMQ │ │ ← production
│ └──────────┘ │
└────────────────┘
Job Categories
| Category | Use case |
|---|---|
'ai' |
LLM inference, embedding generation, model fine-tuning |
'maintenance' |
Database vacuuming, log rotation, cache pruning |
'data' |
ETL pipelines, report generation, aggregation |
'system' |
Health checks, metric aggregation, alert evaluation |
'custom' |
Any domain-specific job not covered above |
Job Priorities
| Priority | Description |
|---|---|
'low' |
Best-effort; runs when the queue is idle |
'normal' |
Default priority for most jobs |
'high' |
Runs before normal and low jobs |
'critical' |
Runs first; use sparingly to avoid starvation |
Priority is communicated to the provider as metadata. BullMQJobProvider maps priorities to BullMQ job priorities natively. InMemoryJobProvider does not reorder queued jobs by priority.
Retry Policies
Jobs that throw an error are retried according to their JobRetryPolicy:
| Strategy | Behaviour |
|---|---|
'none' |
No retries; failure is terminal |
'fixed' |
Retry after initialDelayMs on every attempt |
'exponential' |
Delay doubles with each attempt: initialDelayMs * 2^(attempt-1), capped at maxDelayMs |
The default retry policy (applied when none is specified) is fixed, 3 attempts, 1 s initial delay, 30 s max delay.
Scheduling
| Kind | Description |
|---|---|
'cron' |
Parsed with cron-parser; timezone-aware |
'interval' |
Fixed millisecond interval from last execution |
'immediate' |
Runs once as soon as possible after registration |
'auto' |
Uses intervalMs if set, otherwise defaults to 1 hour |
The JobScheduler computes the next run time using cron-parser for cron expressions and simple arithmetic for intervals. It sets a setTimeout for each job and re-schedules after each execution.
Related Documentation
- Decorators —
@AIBackgroundJob,@AISchedule, and their options - Job Types —
JobDefinition,JobHandler,JobContext,JobResult - Providers —
InMemoryJobProvider,BullMQJobProvider,BackgroundJobRuntime,JobScheduler
Integration Notes
- Job classes are instantiated fresh for every execution via
new definition.target(). Do not store mutable state on a job class; use thecontext.metadatabag instead. - The
JobRegistryis a module-level singleton. In test environments, callresetJobRegistry()between tests to clear registered definitions. - Both the runtime and the scheduler must be started (
await runtime.start(),await scheduler.start()) and stopped (await runtime.stop(),await scheduler.stop()) for graceful lifecycle management.
Pitfalls
JobScheduleronly triggers jobs for definitions that have ascheduleset at the timestart()is called. Definitions registered afterstart()require a call toscheduler.refreshSchedules()to be picked up.- The
InMemoryJobProviderusessetTimeoutinternally. In test environments with fake timers, you must advance the timer or the job will never execute. - Retry delay for
exponentialstrategy caps atmaxDelayMs(default 30 s). For very long retry windows, setmaxDelayMsexplicitly.