Packages
queuetopia
6.0.0
6.0.0
5.0.0
4.0.0
3.0.0
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.3
2.5.2
2.5.1
2.5.0
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.5
2.3.3
2.3.2
2.3.1
2.3.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
2.0.1
2.0.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.1
1.3.0
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.3.0
0.2.0
0.1.2
Persistent blocking job queue
Current section
Files
Jump to
Current section
Files
queuetopia
CHANGELOG.md
CHANGELOG.md
# Changelog
## 6.0.0 - Unreleased
- New `queuetopia_pending_queues` table (migration V8): one row per queue with pending work, whose `next_performable_at` encodes both a head job scheduled later and a post-failure backoff. The migration backfills it from the existing jobs backlog.
- The table is maintained transactionally at every job transition: `create_job` upserts the row in the same transaction as the job insert (keeping the earliest performable time), and job completion recomputes it from the queue's head job — next job's `scheduled_at` on success, the backed-off `next_attempt_at` on failure, row deleted when the queue empties. A stale row value self-heals at the next poll; a **missing** row does not — see Upgrading from 5.x.
- The scheduler polls this table instead of running a `DISTINCT` scan over the whole jobs backlog — poll cost is now proportional to the number of pending queues, not to the backlog size. A stale row (e.g. an optimistic `next_performable_at`) is refreshed on the spot and drops out of subsequent polls.
- The pending row doubles as the queue's internal mutex: refreshes take it with `SELECT ... FOR UPDATE`, so a concurrent `create_job` can no longer race the delete/recompute of the same queue's row — the delete/create races are closed.
- Claiming the next performable job happens in a single transaction: head lookup, queue lock, then a post-lock fresh re-read of the job (`FOR SHARE`) that rolls the claim back if the job was completed in between — closing the cross-node double-execution window; empty or not-yet-performable queues short-circuit before taking a lock.
- **Breaking:** a test seeding a bare job row must also seed the queue's pending row — the scheduler only polls the `queuetopia_pending_queues` table. `Queuetopia.Factories.build(:pending_queue, attrs)` provides the struct.
- **Breaking:** Postgres support is removed — Queuetopia targets MySQL only, version **8.0.1 or later** (the pending-row lock uses `FOR UPDATE NOWAIT`). The `postgrex` dependency, the per-adapter migration branches and the Postgres upsert options are gone.
- **Breaking:** `Queuetopia.Queue` is split into `Queuetopia.Jobs` (creation, claim, perform, results, cleanup), `Queuetopia.PendingQueues` (row maintenance and the poll listing) and `Queuetopia.Locks` (take, release, expire). `Queuetopia.Queue.Job` becomes `Queuetopia.Jobs.Job`; update any code referencing the old modules.
- **Breaking:** several formerly public job predicates (`done?`, `max_attempts_reached?`, the time check) are folded into `performable_now?` or made private; the `Queue` API is narrowed to the claim/get-next surface.
- The dependency requirements are unchanged (see `mix.exs`); the lockfile now resolves the current majors of `ecto`/`ecto_sql`, `myxql` and `decimal`. Consumers should make sure their own resolution takes `decimal` >= 3.0: earlier versions are affected by CVE-2026-32686 (unbounded exponent in `Decimal.new`, DoS).
### Upgrading from 5.x
The scheduler only polls `queuetopia_pending_queues`: a job whose queue has no
pending row is never run, and 5.x producers do not write that row. The upgrade
must be stop-the-world:
1. Stop every node still on 5.x — producers included.
2. Run the migrations (V8 to V10; the V10 backfill seeds the pending rows from
the jobs backlog).
3. Start the 6.0 nodes only.
A job inserted out-of-band later (raw SQL, a revived job, a straggler 5.x
writer) has no pending row and stays invisible to the poll: run
`Queuetopia.Migrations.V10.backfill(MyApp.Repo)` to catch up — idempotent and
replayable at any time.
## 5.0.0 - 2026-09-01
- **Breaking:** job creation is now always silent — `create_job` never wakes the scheduler, and the `notify?:` option (added in 3.0.0) is gone.
- **Breaking:** `handle_event/1` and `listen/1` are replaced by `notify_scheduler/0`. Waking the scheduler is now a deliberate post-commit act of the producer; a forgotten notification is covered by the periodic poll.
## 4.0.0 - 2026-08-31
- **Breaking:** the `scheduler_repo` option (added in 3.0.0) is replaced by an opt-in dedicated scheduler pool: `dedicated_scheduler_pool?: true` in the Queuetopia config (default off) makes the scheduler and the job cleaner query through a second instance of the Queuetopia's repo — same config and adapter, shared by every Queuetopia on that repo. The pool size comes from the `QUEUETOPIA_SCHEDULER_POOL_SIZE` environment variable; enabling the pool without it raises at startup.
## 3.0.0 - 2026-08-31
- **Breaking:** the performer module is now resolved by convention as `<scope>.Performer` at execution time; the `performer` option and the jobs-table column are removed (migration V7 drops the column). Remote producers no longer need to know the executor's performer module.
- `create_job` accepts `notify?: false`, letting callers insert jobs in bulk without waking the scheduler on each insert — a single notification at the end of the batch wakes it once.
- New `scheduler_repo` option: the Scheduler and JobCleaner can run their queries (poll, queue locking, job results, cleanup) through a dedicated repo, so their liveness does not depend on the business pool, and vice versa.