sorentwo

sorentwo

Oban Core Team

Hello!

tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability.

After spending nearly a year building Kiq, an Elixir port of Sidekiq with most of the bells and whistles, I came to the realization that the model was all wrong. Most of us don’t want to rely on Redis for production data, and Sidekiq is a largely proprietary legacy system. Not the best base for a reliable job processing system.

So, I took the best parts of Kiq and some inspiration from EctoJob and put together Oban. The primary goals are reliability , consistency and observability. It is fundamentally different from other background job processing tools because it retains job data for historic metrics and inspection.

Here are some of the marquee features that differentiate it from other job processors that are out there (pulled straight from the README):

  • Isolated Queues — Jobs are stored in a single table but are executed in distinct queues. Each queue runs in isolation, ensuring that a jobs in a single slow queue can’t back up other faster queues.
  • Queue Control — Queues can be paused, resumed and scaled independently at runtime.
  • Job Killing — Jobs can be killed in the middle of execution regardless of which node they are running on. This stops the job at once and flags it as discarded .
  • Triggered execution — Database triggers ensure that jobs are dispatched as soon as they are inserted into the database.
  • Scheduled Jobs — Jobs can be scheduled at any time in the future, down to the second.
  • Job Safety — When a process crashes or the BEAM is terminated executing jobs aren’t lost—they are quickly recovered by other running nodes or immediately when the node is restarted.
  • Historic Metrics — After a job is processed the row is not deleted. Instead, the job is retained in the database to provide metrics. This allows users to inspect historic jobs and to see aggregate data at the job, queue or argument level.
  • Node Metrics — Every queue broadcasts metrics during runtime. These are used to monitor queue health across nodes.
  • Queue Draining — Queue shutdown is delayed so that slow jobs can finish executing before shutdown.
  • Telemetry Integration — Job life-cycle events are emitted via Telemetry integration. This enables simple logging, error reporting and health checkups without plug-ins.

Version v0.2.0 was released today. Please take a look at the README or the docs and let me know what you think!

https://github.com/sorentwo/oban

— Parker


One more thing! A stand-alone dashboard built on Phoenix Live View is in the works.

The killer feature for any job processor is the UI. Every sizable app I know of relies on a web UI to introspect and manage jobs. It is very much a WIP, but here is a preview of the UI running in an environment with constant job generation:

45970 311

Showing Posts 1 to 10

hauleth

hauleth

One suggestion - maybe instead of storing data as a JSONB field instead use binary field type and ETF to store arguments. It will provide you greater flexibility (for example distinction between atom and binary).

sorentwo

sorentwo OP

Oban Core Team

That was my initial inclination as well, but I ended up using JSONB instead of a few reasons:

  1. It makes it much easier to enqueue jobs in other languages. The primary system I work on uses Elixir, Python and Ruby on the backend. It is essential that jobs can be enqueued from outside of Elixir/Erlang.
  2. Searching and filtering is an important part of the UI and historic observation. By storing arguments as JSONB we can actually leverage indexes for full text search. A common situation we have is trying to determine if a job was ran for a particular customer or with particular arguments.

Thanks for the feedback!

22
Post #2
engineeringdept

engineeringdept

This looks really great - good work! Looking forward to trying it out.

bamorim

bamorim

Wow. It looks great. I’ll take a looked here. Gratz

lpil

lpil

Creator of Gleam

I’m glad you went for JSONB rather than ETF for serialisation. :slight_smile:

Rihanna uses ETF and it has repeatedly made gathering data and altering jobs a difficult process that involves loading every row into application memory when I could have run a single SQL statement.

Is the manner in which Oban polls the database documented?

How is failure detection implemented? If you’re using Ecto it suggests that your not using transactions to detect worker death?

Phillipp

Phillipp

I am the weird guy who is amazed by job queues and background processing and I am kinda hyped about this library. I am sad I have no active use case for a job queue at the moment.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

First, I’m very excited to see this! We have a similar home rolled solution but this has all kinds of wonderful features I’d love to have. The argument for using jsonb is sound, people should keep in mind that if there are specific values you want to keep as erlang terms you can always term |> :erlang.term_to_binary |> Base.encode64 manually on that term and put it in the job JSON, OR have those values in a different table that the job json points to.

PG_NOTIFY FYI

I’m noticing you’re using pg_notify I want to make sure you’re aware of an important detail in how pg_notify behaves, because I only learned about it recently and found it quite unexpected. For every invocation of pg_notify within a transaction, pg_notify dedups the notice against existing notices that are set to go out when the transaction commits. This check is O(n^2)!!!. This can cause serious issues if for example you encode the entire job payload in the notice, because 1000 jobs will create 1000 unique notices, and suddenly what was a simple transactional insert of 1000 records has taken several seconds.

Based on a quick glance at the code though it looks like you’re limiting pg_notify usage to just push out {"queue":queue, "state":job_state} json blobs which should be fine, since there are a limited number of queues and a limited number of job states.

Details here:

https://www.postgresql.org/message-id/flat/CAHg_5goMB8TJLBSB%3DsK9cX2CQhi%3DTtzGW1Ph47yev%2ByVgAqgyA%40mail.gmail.com#d3c67e942f11b5118a3081ba1792d979

14
Post #7
outlog

outlog

looks great.. any pointers on pros/cons vs ecto_job | Hex and rihanna | Hex ?

sorentwo

sorentwo OP

Oban Core Team

Glad you can vouch for that choice as well!

Only slightly, in the config documentation for Oban.start_link/1. For most job dispatching it relies on triggers/notifications, but because of scheduled jobs it also polls every one second by default. The poll interval is effectively the scheduled job resolution.

That’s correct, it doesn’t use transactions at all. I routinely run jobs that can last for 15-30 minutes (video encoding, creating zips, etc) and holding a transaction for that long would eat up the connection pool.

There are two modes of failure that it handles:

  1. Standard catch/rescue — The catch/rescue bit is built on top of telemetry. It uses a notification handler to enqueues a retry or discard dead jobs.
  2. Shutdown (Orphans) — Every job acquires an advisory lock when it starts executing. The advistory lock is used to indicate which jobs are actively executing and which really dead. When a node shuts down it takes its connection pool with hit, which cleans up lingering advisory locks. Each node periodically scans for orphaned jobs and marks them as available again for retried execution.

It is easy to tell how effective the orphan rescue is because jobs are kept around after they are complete. Any job that has multiple attempts and no errors must have been orphaned!

lpil

lpil

Creator of Gleam

If a job takes a lock when it starts how is the lock established for retrying a failed job (not a node death)? Wouldn’t you need to release the lock beforehand?

Where Next? Top

Trending in Announcing Top

wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
fuelen
Hi all! I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas. You...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews