Oban Background Jobs Reference
Quick reference for Elixir Oban patterns.
Oban Pro Detection
Before applying patterns, check for Oban Pro:
grep -E "oban_pro|oban_web" mix.exs
grep -r "use Oban.Pro.Worker" lib/
grep -r "Oban.Pro.Engines.Smart" config/
If Oban Pro detected, use Pro patterns for ALL new workers:
| Standard Oban |
Oban Pro |
use Oban.Worker |
use Oban.Pro.Worker |
def perform(%Job{}) |
def process(%Job{}) |
Oban.Testing |
Oban.Pro.Testing |
| Advisory lock engine |
Oban.Pro.Engines.Smart |
Pro features (all optional): args_schema (typed args), Workflows, Batches, Chunks,
Relay, hooks, encryption, deadlines, chaining, Smart Engine (global concurrency + rate limiting).
Pro plugins (DynamicCron, DynamicLifeline, DynamicPruner) enhance OSS equivalents — swap module, don't run both.
See references/oban-pro-basics.md for all patterns and migration guide.
Iron Laws — Never Violate These
- JOBS MUST BE IDEMPOTENT — Safe to retry. Use idempotency keys for payments
- JOBS MUST STORE IDs, NOT STRUCTS — JSON serialization.
%{user_id: 1} not %{user: %User{}}
- JOBS MUST HANDLE ALL RETURN VALUES —
:ok, {:error, _}, {:cancel, _}, {:snooze, _}
- ARGS USE STRING KEYS — Pattern match
%{"user_id" => id} not %{user_id: id}
- UNIQUE CONSTRAINTS FOR USER ACTIONS — Prevent double-click duplicates
- NEVER STORE LARGE DATA IN ARGS — Store references (IDs, paths), not content
- SMART ENGINE: NEVER USE
attempt TO LIMIT SNOOZES — Snooze rolls back attempt counter. Use meta["snoozed"] instead. Causes infinite loops
Quick Worker Template
defmodule MyApp.Workers.ExampleWorker do
use Oban.Worker,
queue: :default,
max_attempts: 5,
unique: [period: {5, :minutes}, keys: [:entity_id]]
@impl Oban.Worker
def perform(%Oban.Job{args: %{"entity_id" => id}}) do
case process(id) do
{:ok, _} -> :ok
{:error, :not_found} -> {:cancel, "Entity not found"}
{:error, :rate_limited} -> {:snooze, {5, :minutes}}
{:error, reason} -> {:error, reason}
end
end
end
Return Value Meanings
| Return |
State |
Behavior |
:ok |
completed |
Success |
{:ok, value} |
completed |
Success with value |
{:error, reason} |
retryable |
Retry with backoff |
{:cancel, reason} |
cancelled |
Stop permanently |
{:snooze, seconds} |
scheduled |
Delay and retry |
Quick Decisions
Which Queue?
- Critical operations → High concurrency (20+)
- Mailers/Webhooks (I/O) → Medium concurrency (30-50)
- CPU-intensive → Low concurrency (3-5)
- External APIs → Use
dispatch_cooldown for rate limiting
Testing Pattern
use Oban.Testing, repo: MyApp.Repo
# Assert enqueued
assert_enqueued worker: MyApp.Worker, args: %{id: 1}
# Execute and verify
assert :ok = perform_job(MyApp.Worker, %{id: 1})
Common Anti-patterns
| Wrong |
Right |
%{user_id: id} pattern match |
%{"user_id" => id} (string keys) |
%{user: %User{}} in args |
%{user_id: 1} (IDs only) |
| No idempotency for payments |
Use idempotency keys |
| Ignoring return values |
Handle all outcomes explicitly |
References
For detailed patterns, see:
references/worker-patterns.md - Worker options, backoff, timeout
references/queue-config.md - Queue design, pool sizing, cron, Smart Engine
references/testing-patterns.md - Testing, assertions, drain (OSS + Pro)
references/oban-pro-basics.md - Pro.Worker, Workflow, Batch, Chunk, Relay, plugins
1---2name: oban3description: Oban job processing — workers, perform/1 (OSS) and process/1 (Pro), queues, cron, retries, unique jobs, idempotency, Oban Pro (Workflow, Batch, Chunk, Smart Engine), Testing. Use when writing Oban workers, queue config, or debugging jobs.4---5
6# Oban Background Jobs Reference
7
8Quick reference for Elixir Oban patterns.
9
10## Oban Pro Detection
11
12**Before applying patterns, check for Oban Pro:**
13
14```bash
15grep -E "oban_pro|oban_web" mix.exs
16grep -r "use Oban.Pro.Worker" lib/
17grep -r "Oban.Pro.Engines.Smart" config/
18```
19
20**If Oban Pro detected**, use Pro patterns for ALL new workers:
21
22| Standard Oban | Oban Pro |
23|---------------|----------|
24| `use Oban.Worker` | `use Oban.Pro.Worker` |
25| `def perform(%Job{})` | `def process(%Job{})` |
26| `Oban.Testing` | `Oban.Pro.Testing` |
27| Advisory lock engine | `Oban.Pro.Engines.Smart` |
28
29**Pro features** (all optional): `args_schema` (typed args), Workflows, Batches, Chunks,
30Relay, hooks, encryption, deadlines, chaining, Smart Engine (global concurrency + rate limiting).
31Pro plugins (DynamicCron, DynamicLifeline, DynamicPruner) **enhance** OSS equivalents — swap module, don't run both.
32See `references/oban-pro-basics.md` for all patterns and migration guide.
33
34---
35
36## Iron Laws — Never Violate These
37
381. **JOBS MUST BE IDEMPOTENT** — Safe to retry. Use idempotency keys for payments
392. **JOBS MUST STORE IDs, NOT STRUCTS** — JSON serialization. `%{user_id: 1}` not `%{user: %User{}}`
403. **JOBS MUST HANDLE ALL RETURN VALUES** — `:ok`, `{:error, _}`, `{:cancel, _}`, `{:snooze, _}`
414. **ARGS USE STRING KEYS** — Pattern match `%{"user_id" => id}` not `%{user_id: id}`
425. **UNIQUE CONSTRAINTS FOR USER ACTIONS** — Prevent double-click duplicates
436. **NEVER STORE LARGE DATA IN ARGS** — Store references (IDs, paths), not content
447. **SMART ENGINE: NEVER USE `attempt` TO LIMIT SNOOZES** — Snooze rolls back attempt counter. Use `meta["snoozed"]` instead. Causes infinite loops
45
46## Quick Worker Template
47
48```elixir
49defmodule MyApp.Workers.ExampleWorker do
50 use Oban.Worker,
51 queue: :default,
52 max_attempts: 5,
53 unique: [period: {5, :minutes}, keys: [:entity_id]]
54
55 @impl Oban.Worker
56 def perform(%Oban.Job{args: %{"entity_id" => id}}) do
57 case process(id) do
58 {:ok, _} -> :ok
59 {:error, :not_found} -> {:cancel, "Entity not found"}
60 {:error, :rate_limited} -> {:snooze, {5, :minutes}}
61 {:error, reason} -> {:error, reason}
62 end
63 end
64end
65```
66
67## Return Value Meanings
68
69| Return | State | Behavior |
70|--------|-------|----------|
71| `:ok` | `completed` | Success |
72| `{:ok, value}` | `completed` | Success with value |
73| `{:error, reason}` | `retryable` | Retry with backoff |
74| `{:cancel, reason}` | `cancelled` | Stop permanently |
75| `{:snooze, seconds}` | `scheduled` | Delay and retry |
76
77## Quick Decisions
78
79### Which Queue?
80
81- **Critical operations** → High concurrency (20+)
82- **Mailers/Webhooks (I/O)** → Medium concurrency (30-50)
83- **CPU-intensive** → Low concurrency (3-5)
84- **External APIs** → Use `dispatch_cooldown` for rate limiting
85
86### Testing Pattern
87
88```elixir
89use Oban.Testing, repo: MyApp.Repo
90
91# Assert enqueued
92assert_enqueued worker: MyApp.Worker, args: %{id: 1}
93
94# Execute and verify
95assert :ok = perform_job(MyApp.Worker, %{id: 1})
96```
97
98## Common Anti-patterns
99
100| Wrong | Right |
101|-------|-------|
102| `%{user_id: id}` pattern match | `%{"user_id" => id}` (string keys) |
103| `%{user: %User{}}` in args | `%{user_id: 1}` (IDs only) |
104| No idempotency for payments | Use idempotency keys |
105| Ignoring return values | Handle all outcomes explicitly |
106
107## References
108
109For detailed patterns, see:
110
111- `references/worker-patterns.md` - Worker options, backoff, timeout
112- `references/queue-config.md` - Queue design, pool sizing, cron, Smart Engine
113- `references/testing-patterns.md` - Testing, assertions, drain (OSS + Pro)
114- `references/oban-pro-basics.md` - Pro.Worker, Workflow, Batch, Chunk, Relay, plugins