Job Priorities
Not all jobs are equal
A password reset email should be sent immediately. A weekly sales report can wait. A payment retry is urgent. An analytics sync is not. Without priorities, all jobs wait in the same line — a flood of report-generation jobs delays time-sensitive password resets.
The priority column
The jobs table has a priority column (INTEGER, default 0). Higher numbers mean higher priority. The dequeue query already sorts by priority:
ORDER BY priority DESC, scheduled_at ASC Higher-priority jobs are claimed first. Among jobs with the same priority, older jobs (earlier scheduled_at) go first — FIFO within each priority level.
Defining priority levels
// src/priorities.ts
export const PRIORITY = {
LOW: 0, // Analytics, cleanup, reports
NORMAL: 5, // Standard notifications, syncs
HIGH: 10, // Payment processing, important emails
CRITICAL: 20, // Password resets, security alerts
} as const; // Usage
enqueue("send_password_reset", { userId, token }, { priority: PRIORITY.CRITICAL });
enqueue("send_order_confirmation", { orderId }, { priority: PRIORITY.HIGH });
enqueue("generate_daily_report", { date }, { priority: PRIORITY.LOW });
enqueue("sync_inventory", { productId }, { priority: PRIORITY.NORMAL }); How priority affects ordering
-- Queue state (priority DESC, scheduled_at ASC):
-- 1. password_reset priority=20 scheduled_at=10:00:05 ← processed first
-- 2. payment_retry priority=10 scheduled_at=10:00:01 ← second
-- 3. order_confirmation priority=10 scheduled_at=10:00:03 ← third (same priority, older first)
-- 4. analytics_sync priority=0 scheduled_at=09:55:00 ← last (low priority) The password reset runs first despite being enqueued last, because it has the highest priority. The analytics sync was enqueued 5 minutes earlier but runs last because of its low priority.
Starvation prevention
A constant stream of high-priority jobs could starve low-priority jobs — they never run because something higher-priority is always ahead. In practice, this is rare for most applications. If it happens, two strategies help:
Priority aging: Increase a job’s priority the longer it waits:
-- Bump priority for jobs waiting more than 10 minutes
UPDATE jobs
SET priority = priority + 1
WHERE status = 'pending'
AND scheduled_at < datetime('now', '-10 minutes')
AND priority < 20; Dedicated workers: Run separate workers for different priority levels. One worker handles only CRITICAL and HIGH jobs. Another handles NORMAL and LOW. This guarantees low-priority jobs get processing time.
Exercises
Exercise 1: Enqueue 5 jobs with different priorities. Verify the worker processes them in priority order.
Exercise 2: Enqueue a LOW priority job, then a HIGH priority job. Verify the HIGH job runs first even though it was enqueued later.
Exercise 3: Implement priority aging. Enqueue a LOW priority job. Wait 10 minutes (or set the threshold to 10 seconds for testing). Verify its priority increases.
Why does the dequeue query sort by priority DESC then scheduled_at ASC?