Docs/background jobsv1.0.0-rc.1

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 @AIBackgroundJob that implements a execute(context) method. The decorator registers the class in a global JobRegistry at module load time.
  • Provider — the queue backend. Two providers ship with UDIOT: InMemoryJobProvider for local development and testing, and BullMQJobProvider for production workloads backed by Redis.
  • Runtime — BackgroundJobRuntime is the engine. It accepts a provider, exposes scheduleJob(), and wraps every execution in an OpenTelemetry trace span.
  • Scheduler — JobScheduler reads 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.process span. 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.

  • 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 the context.metadata bag instead.
  • The JobRegistry is a module-level singleton. In test environments, call resetJobRegistry() 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

  • JobScheduler only triggers jobs for definitions that have a schedule set at the time start() is called. Definitions registered after start() require a call to scheduler.refreshSchedules() to be picked up.
  • The InMemoryJobProvider uses setTimeout internally. In test environments with fake timers, you must advance the timer or the job will never execute.
  • Retry delay for exponential strategy caps at maxDelayMs (default 30 s). For very long retry windows, set maxDelayMs explicitly.
From the canonical UDIOT public documentation.Try it in the playground ↗