We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Pause an Oban job with `{:snooze, seconds}`
almirsarajcic
Sometimes a job isn’t ready to run yet — a third-party API is rate-limiting you, a resource hasn’t been created, or you just need to wait a few minutes before retrying. Returning {:snooze, seconds} from perform/1 delays the job without counting it as a failure.
defmodule MyApp.Workers.SyncWorker do
use Oban.Worker, queue: :default, max_attempts: 5
@impl Oban.Worker
def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
case MyApp.API.fetch_user(user_id) do
{:ok, user} ->
MyApp.Sync.run(user)
{:error, :rate_limited} ->
{:snooze, 60}
{:error, reason} ->
{:error, reason}
end
end
end
The job is rescheduled seconds from now and marked as snoozed in the database — not retryable, not discarded. It won’t appear as a failure in your error tracker and won’t count toward max_attempts.
The seconds value must be a non-negative integer. Snoozing indefinitely with large values is valid but consider :cancel if the job should never run again.
All perform/1 return values:
:ok / {:ok, value} # job succeeds
{:snooze, seconds} # delayed, no failure recorded
{:error, reason} # failure, counts toward max_attempts
{:cancel, reason} # stopped permanently, no more retries
Note: :discard and {:discard, reason} are deprecated — use {:cancel, reason} instead.
OSS caveat: attempt counter increments on snooze
In Oban OSS, snoozing increments both attempt and max_attempts by one to preserve the remaining retry budget. This means job.attempt and job.max_attempts are both higher than you’d expect after a snooze. If your backoff/1 callback or business logic reads job.attempt, you need to account for this:
defmodule MyApp.Workers.SyncWorker do
use Oban.Worker, queue: :default, max_attempts: 5
@max_attempts 5
@impl Oban.Worker
def backoff(%Oban.Job{} = job) do
corrected_attempt = @max_attempts - (job.max_attempts - job.attempt)
trunc(:math.pow(corrected_attempt, 4) + 15 + :rand.uniform(30) * corrected_attempt)
end
@impl Oban.Worker
def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
case MyApp.API.fetch_user(user_id) do
{:ok, user} -> MyApp.Sync.run(user)
{:error, :rate_limited} -> {:snooze, 60}
{:error, reason} -> {:error, reason}
end
end
end
Oban Pro handles this cleanly without the attempt inflation — if you’re on Pro, snoozing just works as you’d expect.
copied to clipboard