Upgrading to v1.8

This release introduces workflow compensations, cursor-based backfills, inline cron annotations, external recorded storage, and persisted pruning rules with archiving. It also adopts Oban's dedicated service configuration and replaces the chain and chunk indexes for improved performance.

A database migration is required. All configuration changes are optional and can be adopted incrementally.

Bump Your Deps

Update Oban and Pro to the latest versions:

{:oban, "~> 2.24"},
{:oban_pro, "~> 1.8.0", repo: "oban"},

Run Oban.Pro.Migration (Required)

Oban Pro v1.8 drops _old indexes created by the v1.7 migration, adds tables for persisted Oban.Pro.Pruner rules and archived jobs, columns for workflow compensations, and an index for backfills. Generate a new migration and run Oban.Pro.Migration:

defmodule MyApp.Repo.Migrations.UpgradeObanProToV180 do
  use Ecto.Migration

  def up, do: Oban.Pro.Migration.up(version: "1.8.0")
  def down, do: Oban.Pro.Migration.down(version: "1.8.0")
end

On large oban_jobs tables, split the schema and index changes so indexes are created concurrently without blocking writes:

defmodule MyApp.Repo.Migrations.UpgradeObanProSchemasToV180 do
  use Ecto.Migration

  def up, do: Oban.Pro.Migration.up(version: "1.8.0", only: :schemas)
  def down, do: Oban.Pro.Migration.down(version: "1.8.0", only: :schemas)
end

Then run the index migration outside a transaction:

defmodule MyApp.Repo.Migrations.UpgradeObanProIndexesToV180 do
  use Ecto.Migration

  @disable_migration_lock true
  @disable_ddl_transaction true

  def up, do: Oban.Pro.Migration.up(version: "1.8.0", only: :indexes)
  def down, do: Oban.Pro.Migration.down(version: "1.8.0", only: :indexes)
end

The migration lock doesn't need to be disabled when the repo uses advisory migration locks.

The Smart engine is now Oban.Pro.Engine. Update your configuration to use the shorter module name:

 config :my_app, Oban,
-  engine: Oban.Pro.Engines.Smart
+  engine: Oban.Pro.Engine

The old module remains available for compatibility and won't emit deprecation warnings.

Oban v2.24 adds top-level options for cron, lifeline, and pruner, while queues accepts the same {module, options} form. Move the corresponding Pro modules out of :plugins and use their shorter names:

 config :my_app, Oban,
-  queues: false,
-  plugins: [
-    {Oban.Pro.Plugins.DynamicCron, crontab: [...]},
-    Oban.Pro.Plugins.DynamicLifeline,
-    {Oban.Pro.Plugins.DynamicPruner, mode: {:max_age, {7, :days}}},
-    {Oban.Pro.Plugins.DynamicQueues, queues: [default: 10]}
-  ]
+  cron: {Oban.Pro.Cron, crontab: [...]},
+  lifeline: Oban.Pro.Lifeline,
+  pruner: {Oban.Pro.Pruner, mode: {:max_age, {7, :days}}},
+  queues: {Oban.Pro.Queues, queues: [default: 10]}

The old modules and :plugins configuration remain available and won't emit deprecation warnings on startup. However, calls to runtime management functions on the old modules are deprecated, and you should use the corresponding functions on Oban.Pro.Cron, Oban.Pro.Pruner, or Oban.Pro.Queues instead.

Replace Pruner Overrides with Rules (Optional)

The mode and *_overrides options still work and are automatically translated into rules. To use compound matches, explicit ordering, runtime updates, or archiving, replace all legacy options with rules at the same time:

 pruner: {
   Oban.Pro.Pruner,
-  mode: {:max_age, {7, :days}},
-  queue_overrides: [events: {:max_age, {10, :minutes}}],
-  state_overrides: [discarded: {:max_age, {2, :days}}],
-  worker_overrides: ["MyApp.SecretWorker": {:max_age, {1, :second}}]
+  rules: [
+    [name: "queue-events", queue: :events, max_age: {10, :minutes}],
+    [name: "state-discarded", state: :discarded, max_age: {2, :days}],
+    [name: "worker-MyApp.SecretWorker", worker: MyApp.SecretWorker, max_age: {1, :second}],
+    [name: "default", max_age: {7, :days}]
+  ]
 }

Rules use first-match precedence, so preserve the legacy queue, state, worker, and default order unless you intentionally want different behavior. Don't combine rules with mode or any *_overrides; mixed configuration prevents the Pruner from starting.

Add the Mix Compiler for Cron Annotations (Optional)

Cron discovery is registered at compile time by a Mix compiler. Add :oban_pro to your project's compilers: list after the standard Mix compilers so the worker beams exist when it scans:

def project do
  [
    ...,
+    compilers: Mix.compilers() ++ [:oban_pro]
  ]
end

In umbrella projects, add it to each child app that defines annotated workers.

If a worker or decorated function uses cron: without the compiler in place, the first compile will warn you with the same instructions.

Pair Cron Annotations with Automatic Sync (Optional)

When you start using cron: on workers or decorators, also set sync_mode: :automatic on the plugin so removed or renamed annotations clean up their persisted entries:

cron: {
  Oban.Pro.Cron,
+  sync_mode: :automatic,
  crontab: [...]
}

Without automatic sync, removing a cron: annotation leaves an orphaned database row that has to be deleted manually (or with a one-off delete: true entry).

See Discovering Annotated Workers and Automatic Synchronization for full details.

Switch Chunks to process_chunk/1 (Optional)

Chunk workers now have a dedicated process_chunk/1 callback that receives the list of jobs. Defining process/1 with a list argument continues to work, but process_chunk/1 is preferred because it doesn't overload the single-job callback shared by all Pro workers.

-@impl Oban.Pro.Worker
+@impl Oban.Pro.Chunk
-def process([_ | _] = jobs) do
+def process_chunk(jobs) do
   ...
 end

Add the Pruner Hash Column (RC Only)

Skip this section unless you ran Oban.Pro.Migration on a v1.8 release candidate. Pruner rules now store a hash to track config changes, and the oban_pruners table created in the release candidates lacks that column. The v1.8.0 migration won't add it because the table already exists, and the Pruner will fail to start without it.

Generate a migration to add the column:

defmodule MyApp.Repo.Migrations.AddHashToObanPruners do
  use Ecto.Migration

  def change do
    alter table(:oban_pruners) do
      add_if_not_exists :hash, :text
    end
  end
end