Oban — Reliable and Observable Job Processing

This week the course Sidekiq in Practice is available for 5$ (it costs 50$ normally).

Looks like the sections about scaling and idempotency may be interesting regardless of the background job framework you are using. Does anyone have an opinion on this course or know if some of the lessons can be applied to Oban as well?

EDIT: don’t have any personal or professional affiliation with the course or with Sidekiq. Just used it in a Rails project and found it incredibly stable and well written piece of software.

1 Like

I would imagine that the sections about scaling are probably not relevant since the runtime characteristics of Ruby and Elixir are so different. The idempotency stuff may be relevant.

1 Like

Hi all, I’m getting stuck into using Oban in my project and would like a bit of advice on cancelling jobs according to tags when the related entity is deleted. I saw this touched upon in a much earlier post in this thread but am wondering if there’s an easier way to query jobs by tag.

E.g I have a worker for scheduling “reminders” to be sent in the future: MyApp.SendReminder. When these are inserted they’re tagged with the related entity (e.g. entity:123).

Say entity with ID 123 is deleted, I obviously no longer want these reminder jobs to run. I’ve found cancel_all_jobs/2 but I’m not entirely sure how to query for jobs with the appropriate tag. It looks like the tags are stored as a string in the DB. Are there any helpers to get jobs by a tag?

Tags are stored as a text array in the database. There are two approaches you can take:

A) Use a query to cancel the jobs and stop them from running, as you mentioned. That query could look like this:

Oban.Job
|> where([j], "entity:123" in j.tags)
|> Oban.cancel_all_jobs()

B) Check the tags as the job executes and make it a no-op. You could check within the perform/1 function like this:

def perform(%Job{args: args, tags: ["entity:" <> entity_id}) do
  case MyApp.Repo.get(Entity, entity_id) do
    %Entity{} -> ...
    nil -> ...
  end
end

The direction you take is up to you and probably depends on the volume of jobs you have. With a handful of extraneous jobs, I’d favor option B.

4 Likes

Many thanks. My use case is actually a little more complex than I described, so I went with option A, but the noop pattern is useful for some other cases I have.

Thanks for the very handy library!

Oban v2.11, Pro v0.10, and Web v2.9 are out and we’ve written a proper announcement post—Oban v2.11, Pro v0.10, and Web v2.9 Released.

Take a look, or browse the CHANGELOG to see what’s new.

8 Likes

Hi, is there still free a dashboard available or is it just the Web paid version? I might not be your target demographic, but do you have any startup pricing plans? my total monthly server costs is less than $20 while Oban is $39 a month.

Oban v2.12 and Oban Pro v0.11 are out!

These releases were dedicated to enriching the testing experience and expanding config, plugin, and queue validation across all environments. They’re so focused on testing that we’ve dubbed them the “don’t forget to floss” and “eat your veggies” editions, respectively :laughing:

:gem: Oban v2.12

Testing Modes

Testing modes bring a new, vastly improved way to configure Oban for testing. The new testing option explicitly states that Oban should operate in a restricted mode for the given environment.

Behind the scenes, the new testing modes rely on validation layers within Oban’s Config module. Now production configuration is validated automatically during test runs. Even though queues and plugins aren’t started in the test environment, their configuration is still validated.

To switch, stop overriding plugins and queues and enable a testing mode in your test.exs config:

config :my_app, Oban, testing: :manual

Testing in :manual mode is identical to testing in older versions of Oban: jobs won’t run automatically, so you can use helpers like assert_enqueued and execute them manually with Oban.drain_queue/2.

An alternate :inline allows Oban to bypass all database interaction and run jobs immediately in the process that enqueued them.

config :my_app, Oban, testing: :inline

Finally, new testing guides cover test setup, unit testing workers, integration testing queues, and testing dynamic configuration.

Global Peer Module

Oban v2.11 introduced centralized leadership via Postgres tables. However,
Postgres-based leadership isn’t always a good fit. For example, an ephemeral leadership mechanism is preferred for integration testing.

In that case, you can make use of the new :global powered peer module for leadership:

config :my_app, Oban,
  peer: Oban.Peers.Global,
  ...

For other improvements and minor bug fixes, view the complete Oban CHANGELOG.

:sparkles: Oban Pro v0.11

In league with Oban v2.12, Pro v0.11 focused extensively on testing improvements.

Pro Testing Module

The centerpiece of those improvements is the Oban.Pro.Testing module, a drop-in replacement for Oban.Testing with considerations for unit, integration, and acceptance testing Pro workers.

The helpers provided by Oban.Pro.Testing are dogfooded—that is, they are what Pro uses for testing internally!

Here’s a taste of how one of the new testing helpers, run_workflow/2, makes it simple to run a workflow optimally, inline, without any extra configuration:

workflow =
  MyWorkflow.new_workflow()
  |> MyWorkflow.add(:com, MyWorkflow.new(%{text: text, mode: :complexity}))
  |> MyWorkflow.add(:sen, MyWorkflow.new(%{text: text, mode: :sentiment}))
  |> MyWorkflow.add(:syn, MyWorkflow.new(%{text: text, mode: :syntax}))
  |> MyWorkflow.add(:exp, MyWorkflow.new(%{}), deps: [:com, :sen, :syn])

# Using with_summary: false gives us a list of executed jobs, including the
# job's recorded result.
assert [_com, _sen, _syn, exp_job] = run_workflow(workflow, with_summary: false)

assert {:ok, 0.8} = MyWorkflow.fetch_result(exp_job)

Furthermore, there are new guides to introduce Pro testing, and walk you through testing Pro workers.

Fewer Plugins, Same Functionality

In an effort to simplify testing scenarios, we’ve pulled some functionality out of plugins and made that functionality available out of the box, without any extra configuration.

The BatchManager and Relay plugins are deprecated and it’s no longer necessary to run them at all! Each deprecated plugin will emit a warning when your app starts, so you’ll probably want to remove them:

config :my_app, Oban, plugins: [
- Oban.Pro.Plugins.BatchManager,
- Oban.Pro.Plugins.Relay
]

Enhanced Workflow Tools

Workflow workers now expose all_workflow_jobs/2 for fetching other jobs in a workflow without streaming. It operates in three modes: fetch all jobs in the workflow, only the current job’s dependencies, or only specific dependencies by name. The same options are now supported by stream_workflow_jobs/2 as well, so you can switch to streaming for large workflows.

Check the Pro CHANGELOG for a complete list of enhancements and bug fixes.

Find us in #oban on Elixir Slack or ask here if you need any help!

6 Likes

Hi @sorentwo , a quick question: The backoff calback must return a number of seconds to wait after the previous attempt, right? Or the seconds relative to the job first enqueing date?

Thank you.

I am trying to compute a simple backoff that would make 20 attempts in about 48 hours, I found this site : Exponential Backoff Calculator but all amounts are actually from the first date.

If it is from the previous attempt then this works:

    Stream.cycle([15])
    |> Stream.scan(fn a, acc -> floor(a + acc * 1.4865) end)
    |> Enum.at(attempt - 1)
    |> Kernel.+(Enum.random(1..5))
1 Like

It’s the number of seconds to wait after the current attempt finishes. Think of it as “now + backoff”.

1 Like

Great, thank you.