Skip to content

Oban Pro Releases

Version: v0.7

Jump to release history


What’s New in v0.7

This release adds a rules-based Pro Pruner, chunk workers for processing jobs in groups, and an Erlang notifier for direct pub/sub without Postgres, alongside fixes for global limits and acks across nodes. See the v0.7 Upgrade Guide for complete upgrade steps and migration caveats.

Pro Pruner

The Pro Pruner replaces the standard pruner with rules that retain specific queues, workers, or states differently, by age or by length. Each job is pruned by the first rule that matches it, and an implicit "default" rule catches everything else, keeping finished jobs for one day:

[pruner]
schedule = "*/5 * * * *"
[[pruner.rules]]
name = "events"
queue = "events"
max_len = 10_000
[[pruner.rules]]
name = "discarded"
state = "discarded"
max_age = "7 days"
archive = true

Rules run on a cron schedule, set their own limit and timeout, and can archive jobs to oban_jobs_archive rather than deleting them. Jobs in an active workflow are preserved by default, so pruning never breaks a running workflow. Rules are stored in the database and can be inspected and changed at runtime through oban_pro.pruner, without a restart:

from oban_pro import pruner
await pruner.insert(name="media", queue="media", max_age="1 week")
await pruner.update("media", paused=True)

The tables match Oban Pro for Elixir, so rules can be managed from Oban Web and shared with Elixir nodes.

Chunks

Chunk workers process jobs together in groups, running when a chunk reaches its size or after a wait time, e.g. 100 jobs or every second. Declare one with the chunk option and define process_chunk, which receives the list of jobs:

from oban import worker
from oban_pro import chunk
@worker(queue="messages", chunk={"size": 100, "wait": 1})
class SendMessages:
async def process_chunk(self, jobs):
responses = await messaging.send_batch([job.args for job in jobs])
bad_token = [job for job, resp in zip(jobs, responses) if resp.bad_token]
return chunk.Cancel("bad token", bad_token)

Every job in a chunk completes unless process_chunk says otherwise. Return chunk.Cancel, chunk.Error, or chunk.Snooze to apply an outcome to particular jobs, or raise to fail the whole chunk. Chunks are grouped by queue and worker by default, or by args and meta with the by option, using the same format as queue partitioning.

Erlang Notifier

The Erlang notifier replaces Postgres LISTEN/NOTIFY with direct pub/sub between processes. It avoids the 8000 byte payload limit, works through transaction poolers, and keeps notification traffic off the database:

[notifier]
type = "erlang"

It runs entirely in Python, with no Erlang or Elixir required. Python processes discover each other through queue producers, and because it speaks the Erlang distribution protocol, it also joins clusters with Elixir nodes running Oban.Notifiers.PG.

Release History

Enhancements

  • [Core] Support Python 3.15

  • [Core] Use consistent wording for errors and logs

    Validation errors follow a single "x must be y, got z" style, log messages are capitalized, and every logger is named after its module.

  • [Pruner] Add the rules-based Pro Pruner

    Rules prune specific queues, workers, or states by age or length, run on a cron schedule in a configurable timezone, and can archive jobs rather than delete them. Rules are stored in the oban_pruners table, reconciled with configuration on start, and managed at runtime through oban_pro.pruner.

  • [Chunk] Add chunk workers for processing jobs in groups

    Chunks run when they reach a size or after a wait time, can be grouped by worker, args, or meta, and apply outcomes to individual jobs with chunk.Cancel, chunk.Error, and chunk.Snooze.

  • [Notifier] Add an Erlang notifier

    A pure Python pub/sub notifier that speaks the Erlang distribution protocol, discovers Python peers automatically, and interoperates with Elixir's Oban.Notifiers.PG.

Bug Fixes

  • [Producer] Fix global limits being exceeded across nodes

    The advisory lock that serializes fetching for queues with a global limit was keyed with Python's randomized string hash, so each process took a different lock and concurrent fetches across nodes could exceed the limit. The key is now hashed in Postgres, so every node uses the same lock.

  • [Producer] Retain state when restoring producer records

    Producers restored after a database reset keep their global limit, rate limit, and partition state rather than reverting to defaults, preventing duplicate execution and global limit overruns after a crash or failover.

  • [Query] Prevent stale acks from overwriting current state

    Acking a job now requires it to still be executing the same attempt. Previously, a job cancelled while running could be flipped to completed by its ack, and a late ack from a rescued job could overwrite a newer execution on another node.

  • [Unique] Include suspended jobs in unique states

    Suspended jobs didn't block duplicate inserts. The incomplete and all groups now include them, and oban_state_to_bit matches the function installed by Oban Pro for Elixir.