Upgrading to v2.0

This information is extracted and expanded from the CHANGELOG.

This is a big release with a ton of new features and fixes, but also a few breaking changes. This guide will walk you through the upgrade process.

Bump Your Deps

Update Oban to the latest version:

  defp deps do
      {:oban, "~> 2.0.0"}

If you're an Oban Web+Pro user you'll also need to bump ObanWeb and add ObanPro:

  defp deps do
      {:oban, "~> 2.0.0"}
      {:oban_web, "~> 2.0.0", organization: "oban"}
      {:oban_pro, "~> 0.3.0", organization: "oban"}

Oban.Worker Changes

The perform/2 callback is replaced with perform/1, where the only argument is an Oban.Job struct. This unifies the interface for all Oban.Worker callbacks and helps to eliminate confusion around pattern matching on arguments.

Change all worker definitions from accepting args and a job to only accept a job and match on the nested args key:

- def perform(%{"id" => id}, _job)
+ def perform(%Job{args: %{"id" => id}})

For finer control of job backoff the backoff/1 callback now expects a job struct instead of an integer. Change any workers that expect an attempt number to match on the full job struct:

- def backoff(attempt)
+ def backoff(%Job{attempt: attempt})

Update Your Config

The :verbose setting was renamed to :log to better align with values accepted by Ecto.Repo.

config :my_app, Oban,
- verbose: false,
+ log: false

Pruning is now handled by the new plugin system. Replace :prune in your config and pass a :max_age value to the plugin:

config :my_app, Oban,
- prune: {:max_age, 60},
- prune_interval: 30_000,
- prune_limit: 5000,
+ plugins: [{Oban.Plugins.Pruner, max_age: 60}]

In test mode you can disable pruning by setting plugins to false instead:

config :my_app, Oban,
-  prune: false,
+  plugins: false

🌟 ObanPro users may opt to use the DynamicPruner instead for finer control. For example, to set per-state retention periods:

config :my_app, Oban,
  plugins: [{
    state_overrides: [
      completed: {:max_age, {1, :day}},
      discarded: {:max_age, {1, :month}}

Pulse tracking and orphaned job rescue are removed from base Oban. This change means that you will need to manually rescue any jobs left executing state after a crash or forced shutdown.

Remove any :beats_maxage, :rescue_after or :rescue_interval settings from your config:

config :my_app, Oban,
-  beats_maxage: 10,
-  rescue_after: 30_000,
-  rescue_interval: 5_000

🌟 ObanPro users may use the Lifeline plugin to retain automatic orphaned job rescue with lightweight heartbeat recording:

config :my_app, Oban,
  plugins: [Oban.Pro.Plugins.Lifeline]

Update Your Tests

The new perform_job test helper automates validating, normalizing and perform jobs in unit tests.

To update your tests replace any calls to perform with the new helper:

- assert :ok = MyApp.Worker.perform(%{id: 1}, %Oban.Job{})
+ assert :ok = perform_job(MyApp.Worker, %{id: 1})

The perform_job helper will verify the worker, the arguments and any provided options. It will then verify that your worker returns a valid result and return the value for you to assert on.

Within integration tests replace drain_queue/3 with drain_queue/2:

- Oban.drain_queue(:myqueue, with_safety: false)
+ Oban.drain_queue(queue: :myqueue, with_safety: false)

Update Telemetry

Telemetry event names have changed, along with some of the metadata and timing units. The new event names are consistent and align with the now-standard telemetry:span conventions.

Update handlers to and match on the new event names:

- def handle_event([:oban, :failure], measure, meta, _) do
+ def handle_event([:oban, :job, :exception], measure, meta, _) do

And update the attach calls:

- :telemetry.attach("oban", [[:oban, :failure]], &handle_event/4, %{})
+ :telemetry.attach("oban", [[:oban, :job, :exception]], &handle_event/4, %{})

Finally, replace references to meta.stack with meta.stacktrace:

- Sentry.capture_exception(meta.error, stacktrace: meta.stack, extra: extra)
+ Sentry.capture_exception(meta.error, stacktrace: meta.stacktrace, extra: extra)

Here is a conversion chart for any other handlers you may have:

[:oban, :started] -> [:oban, :job, :start]
[:oban, :success] -> [:oban, :job, :stop]
[:oban, :failure] -> [:oban, :job, :exception]
[:oban, :trip_circuit] -> [:oban, :circuit, :trip]
[:oban, :open_circuit] -> [:oban, :circuit, :open]

Update ObanWeb (Optional)

ObanWeb is streamlined to share configuration with Oban and use the new plugin system. Remove any ObanWeb specific configuration:

- config :my_app, ObanWeb, repo: MyApp.Repo

Next, add the necessary plugins to the Oban config:

config :my_app, Oban,
  plugins: [

Now remove ObanWeb from your application's supervision tree:

children = [
  {Oban, oban_opts},
- {ObanWeb, oban_web_opts}

And delete any existing ObanWeb migrations:

- defdelegate up, to: ObanWeb.Migrations
- defdelegate down, to: ObanWeb.Migrations

Finally, use the oban_dashboard macro to easily mount in your router:

+ import Oban.Web.Router

scope "/" do
  pipe_through :browser

-  live "/oban", ObanWeb.DashboardLive, layout: {ObanWeb.LayoutView, "app.html"}
+  oban_dashboard("/oban")