Previous commit (3c988a5f) switched oban_pro to the licensed hex repo at deploy time. Reverting that — keep all three Pro packages vendored so prod image builds don't depend on oban.pro reachability or auth on every CI run. oban_pro 1.7.0 dropped into vendor/oban_pro via `mix hex.package fetch oban_pro 1.7.0 --repo=oban --unpack`. mix.exs goes back to `path: "vendor/oban_pro"` (matching oban_met / oban_web, which were already vendored — and stay vendored since the licensed repo only has older versions of those: 0.1.11 / 2.10.6 vs the 1.1.0 / 2.12.1 we vendor). Schema migration from3c988a5fstays — 1.7.0 tables/indexes are already applied. No code changes.
237 lines
6.7 KiB
Markdown
237 lines
6.7 KiB
Markdown
# Plugins
|
|
|
|
## Available Plugins
|
|
|
|
- `DynamicCron`: Runtime-configurable cron scheduling with cluster-wide coordination
|
|
- `DynamicLifeline`: Rescues orphaned jobs, repairs stuck workflows/chains
|
|
- `DynamicPrioritizer`: Automatically increases priority of waiting jobs
|
|
- `DynamicPruner`: Flexible job retention with per-queue/worker rules
|
|
- `DynamicQueues`: Persists queue configuration across restarts
|
|
- `DynamicScaler`: Auto-scales cloud infrastructure based on queue depth
|
|
|
|
## DynamicCron
|
|
|
|
Runtime-configurable cron with cluster-wide coordination. Unlike standard Oban cron, DynamicCron
|
|
supports runtime updates, guaranteed scheduling, and automatic sync.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicCron,
|
|
timezone: "Etc/UTC",
|
|
sync_mode: :automatic,
|
|
crontab: [
|
|
{"*/5 * * * *", MyApp.Workers.FrequentWorker},
|
|
{"0 0 * * *", MyApp.Workers.DailyWorker, guaranteed: true},
|
|
{"0 9 * * MON", MyApp.Workers.WeeklyWorker, timezone: "America/New_York"},
|
|
{"@reboot", MyApp.Workers.StartupWorker}
|
|
]}
|
|
```
|
|
|
|
### Options
|
|
|
|
- `timezone` — default timezone for all entries (defaults to `"Etc/UTC"`)
|
|
- `sync_mode` — `:automatic` syncs crontab entries to the database on startup, removing stale
|
|
entries; `:manual` requires explicit `insert/1` and `delete/1` calls
|
|
- `guaranteed` — per-entry option that ensures missed jobs are inserted when the system comes back
|
|
online (use for critical scheduled tasks)
|
|
|
|
### Runtime Management
|
|
|
|
```elixir
|
|
alias Oban.Pro.Plugins.DynamicCron
|
|
|
|
# Insert or update entries at runtime
|
|
DynamicCron.insert({"0 * * * *", MyApp.HourlyWorker})
|
|
|
|
# Delete an entry
|
|
DynamicCron.delete(MyApp.HourlyWorker)
|
|
|
|
# Get an entry's details
|
|
DynamicCron.get(MyApp.HourlyWorker)
|
|
|
|
# List all entries
|
|
DynamicCron.all()
|
|
```
|
|
|
|
## DynamicLifeline
|
|
|
|
Rescues orphaned jobs and repairs stuck workflows and chains. Essential for production systems
|
|
where nodes may crash or restart unexpectedly.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicLifeline, retry_discarded: false}
|
|
```
|
|
|
|
### Options
|
|
|
|
- `retry_discarded` — whether to retry discarded workflow/chain jobs when dependencies complete
|
|
|
|
Do not change the `rescue_interval` option. If you see it configured, remove it—the default is
|
|
carefully tuned and changing it causes problems.
|
|
|
|
### What It Rescues
|
|
|
|
- **Orphaned jobs** — jobs stuck in `executing` state after a node crash
|
|
- **Stuck workflows** — workflows blocked by completed dependencies that weren't properly tracked
|
|
- **Stuck chains** — chains that stopped progressing due to missed completion signals
|
|
- **Partition backfill** — repopulates rate limit and global limit partitions after restarts
|
|
|
|
## DynamicPrioritizer
|
|
|
|
Automatically increases priority of jobs that have been waiting too long. Prevents starvation
|
|
of low-priority jobs in busy systems.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicPrioritizer,
|
|
prioritizers: [
|
|
rate: [queue: :default, rate: 1, period: to_timeout(second: 30)],
|
|
once: [queue: :media, threshold: to_timeout(minute: 5), boost: 1]
|
|
]}
|
|
```
|
|
|
|
### Strategies
|
|
|
|
- `rate` — continuously boost priority at a fixed rate while waiting
|
|
- `once` — boost priority once after a threshold wait time
|
|
|
|
### Options
|
|
|
|
- `queue` — target queue (required)
|
|
- `rate` — priority boost per period (for `rate` strategy)
|
|
- `period` — how often to apply the boost (for `rate` strategy)
|
|
- `threshold` — wait time before boosting (for `once` strategy)
|
|
- `boost` — how much to increase priority (for `once` strategy, default: 1)
|
|
|
|
## DynamicPruner
|
|
|
|
Flexible job retention with per-queue and per-worker rules. More powerful than the standard
|
|
Oban pruner with fine-grained control over what gets deleted and when.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicPruner,
|
|
mode: {:max_age, {7, :days}},
|
|
queue_overrides: [
|
|
logs: {:max_age, {1, :day}},
|
|
critical: {:max_age, {30, :days}}
|
|
],
|
|
worker_overrides: [
|
|
MyApp.AuditWorker: {:max_age, {90, :days}},
|
|
MyApp.TempWorker: {:max_age, {1, :hour}}
|
|
]}
|
|
```
|
|
|
|
### Modes
|
|
|
|
- `{:max_age, {count, unit}}` — delete jobs older than the specified age
|
|
- `{:max_length, count}` — keep only the most recent N jobs
|
|
|
|
### Options
|
|
|
|
- `mode` — default retention rule for all jobs
|
|
- `queue_overrides` — per-queue retention rules
|
|
- `worker_overrides` — per-worker retention rules (takes precedence over queue rules)
|
|
- `state` — which job states to prune (default: `[:completed, :cancelled, :discarded]`)
|
|
|
|
## DynamicQueues
|
|
|
|
Persists queue configuration to the database for runtime management. Queues survive restarts
|
|
and can be modified without redeploying.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicQueues,
|
|
sync_mode: :automatic,
|
|
queues: [
|
|
default: 10,
|
|
mailers: [local_limit: 20, rate_limit: [allowed: 100, period: {1, :minute}]],
|
|
exports: [local_limit: 5, only: {:node, :=~, "worker"}]
|
|
]}
|
|
```
|
|
|
|
### Options
|
|
|
|
- `sync_mode` — `:automatic` syncs queue config to database on startup; `:manual` requires
|
|
explicit API calls
|
|
- `queues` — initial queue definitions (same format as standard Oban queues)
|
|
- `only` — restrict queue to matching nodes using `{:node, pattern}` or `{:sys_env, key, pattern}`;
|
|
supports operators `:==`, `:!=`, `:=~` for pattern matching
|
|
|
|
### Runtime Management
|
|
|
|
```elixir
|
|
alias Oban.Pro.Plugins.DynamicQueues
|
|
|
|
# Add or update a queue
|
|
DynamicQueues.insert(:reports, local_limit: 5)
|
|
|
|
# Pause a queue
|
|
DynamicQueues.pause(:reports)
|
|
|
|
# Resume a queue
|
|
DynamicQueues.resume(:reports)
|
|
|
|
# Scale a queue
|
|
DynamicQueues.scale(:reports, local_limit: 20)
|
|
|
|
# Delete a queue
|
|
DynamicQueues.delete(:reports)
|
|
|
|
# List all queues
|
|
DynamicQueues.all()
|
|
```
|
|
|
|
## DynamicScaler
|
|
|
|
Auto-scales cloud infrastructure based on queue depth and throughput. Scales nodes horizontally
|
|
to optimize processing during high traffic and save costs during quiet periods.
|
|
|
|
### Configuration
|
|
|
|
```elixir
|
|
{Oban.Pro.Plugins.DynamicScaler,
|
|
scalers: [
|
|
range: 1..5,
|
|
cloud: MyApp.Cloud
|
|
]}
|
|
```
|
|
|
|
### Options
|
|
|
|
- `cloud` — module implementing the `Oban.Pro.Cloud` behaviour (required)
|
|
- `range` — min..max nodes to scale between (required)
|
|
- `cooldown` — minimum time between scaling events (default: 120 seconds)
|
|
- `queues` — `:all` or list of queues to monitor (default: `:all`)
|
|
- `step` — `:none` or max nodes to scale at once (default: `:none`)
|
|
|
|
### Features
|
|
|
|
- **Predictive scaling** — calculates optimal scale based on recent trends, not just current depth
|
|
- **Multi-cloud** — implement the `Cloud` behaviour for your platform (AWS, Fly, Kubernetes, etc.)
|
|
- **Thrashing prevention** — cooldown period prevents rapid scale up/down cycles
|
|
|
|
### Cloud Behaviour
|
|
|
|
Implement `Oban.Pro.Cloud` for your platform:
|
|
|
|
```elixir
|
|
defmodule MyApp.Cloud do
|
|
@behaviour Oban.Pro.Cloud
|
|
|
|
@impl true
|
|
def init(opts), do: opts
|
|
|
|
@impl true
|
|
def current(_opts), do: {:ok, current_node_count()}
|
|
|
|
@impl true
|
|
def scale(count, _opts), do: scale_to(count)
|
|
end
|
|
```
|