dj-queue 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dj_queue-0.4.0/README.md → dj_queue-0.5.0/PKG-INFO +165 -59
- dj_queue-0.4.0/PKG-INFO → dj_queue-0.5.0/README.md +137 -85
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/admin.py +81 -17
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/api.py +55 -8
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/backend.py +4 -4
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/config.py +28 -4
- dj_queue-0.5.0/dj_queue/contrib/prometheus.py +129 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/dashboard.py +44 -430
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/management/commands/dj_queue_health.py +8 -1
- dj_queue-0.5.0/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +100 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/models/jobs.py +11 -11
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/models/recurring.py +13 -4
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/models/runtime.py +11 -1
- dj_queue-0.5.0/dj_queue/observability.py +476 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/operations/cleanup.py +11 -2
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/operations/concurrency.py +33 -11
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/operations/jobs.py +99 -39
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/operations/recurring.py +12 -3
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/base.py +5 -1
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/scheduler.py +3 -1
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/supervisor.py +20 -5
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +1 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/change_form.html +11 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/dashboard.html +4 -48
- dj_queue-0.5.0/dj_queue/urls.py +16 -0
- dj_queue-0.5.0/dj_queue/views.py +38 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/pyproject.toml +4 -2
- {dj_queue-0.4.0 → dj_queue-0.5.0}/LICENSE +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/db.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/log.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.4.0 → dj_queue-0.5.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dj-queue
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Framework :: Django
|
|
9
|
+
Classifier: Framework :: Django :: 6.0
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Dist: croniter>=6.2.2
|
|
17
|
+
Requires-Dist: django>=6.0.0
|
|
18
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
19
|
+
Requires-Dist: psycopg>=3.3.3 ; extra == 'postgres'
|
|
20
|
+
Requires-Dist: prometheus-client>=0.4.0 ; extra == 'prometheus'
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Project-URL: Homepage, https://github.com/coriocactus/dj_queue
|
|
23
|
+
Project-URL: Repository, https://github.com/coriocactus/dj_queue
|
|
24
|
+
Project-URL: Issues, https://github.com/coriocactus/dj_queue/issues
|
|
25
|
+
Provides-Extra: postgres
|
|
26
|
+
Provides-Extra: prometheus
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
1
29
|
# dj_queue
|
|
2
30
|
|
|
3
31
|
[](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
|
|
@@ -17,7 +45,7 @@ It keeps the queue, live execution state, runtime metadata, and task results in
|
|
|
17
45
|
- immediate, scheduled, recurring, and concurrency-limited work
|
|
18
46
|
|
|
19
47
|
`dj_queue` is inspired by Rails' [Solid Queue](https://github.com/rails/solid_queue),
|
|
20
|
-
|
|
48
|
+
shaped to fit Django's [task backend API](https://docs.djangoproject.com/en/6.0/topics/tasks/).
|
|
21
49
|
|
|
22
50
|
## Why dj_queue
|
|
23
51
|
|
|
@@ -45,20 +73,13 @@ Install the package:
|
|
|
45
73
|
pip install dj-queue
|
|
46
74
|
```
|
|
47
75
|
|
|
48
|
-
|
|
49
|
-
database adapter for you:
|
|
76
|
+
Optional extras:
|
|
50
77
|
|
|
51
78
|
```bash
|
|
52
|
-
pip install "dj-queue[postgres]"
|
|
79
|
+
pip install "dj-queue[postgres]" # psycopg for PostgreSQL + LISTEN/NOTIFY
|
|
80
|
+
pip install "dj-queue[prometheus]" # prometheus_client for /dj_queue/metrics
|
|
53
81
|
```
|
|
54
82
|
|
|
55
|
-
Notes:
|
|
56
|
-
|
|
57
|
-
- `postgres` installs `psycopg`, which Django's PostgreSQL backend and
|
|
58
|
-
`dj_queue`'s optional `LISTEN/NOTIFY` wakeups use
|
|
59
|
-
- for MySQL or MariaDB, install and configure a Django-compatible driver in
|
|
60
|
-
your application following Django's database docs
|
|
61
|
-
|
|
62
83
|
Add `dj_queue` to `INSTALLED_APPS`, register the router, and point Django's task
|
|
63
84
|
backend at `DjQueueBackend`:
|
|
64
85
|
|
|
@@ -138,12 +159,10 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
138
159
|
- queue, process, recurring-task, and semaphore overview
|
|
139
160
|
- backend-aware dashboard and raw changelists
|
|
140
161
|
- queue controls: pause, resume, clear ready
|
|
141
|
-
- job
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- failed-job actions: retry and discard from list and detail views
|
|
162
|
+
- job actions: enqueue a fresh copy of any stored job
|
|
163
|
+
- failed jobs: retry and discard from list and detail views
|
|
164
|
+
- unschedule dynamic recurring tasks
|
|
145
165
|
- queue drill-down pages for state-specific inspection
|
|
146
|
-
- queue drill-down actions: discard ready, scheduled, and blocked jobs; retry or discard failed jobs; enqueue finished jobs again
|
|
147
166
|
|
|
148
167
|
**Dashboard overview**
|
|
149
168
|
|
|
@@ -302,54 +321,30 @@ Runners heartbeat into the queue database. If claimed work is left behind,
|
|
|
302
321
|
Use `python manage.py dj_queue_health` to check whether any fresh runtime
|
|
303
322
|
process rows exist for a backend.
|
|
304
323
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
| Backend | Support level | Notes |
|
|
308
|
-
|---|---|---|
|
|
309
|
-
| PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
|
|
310
|
-
| MySQL 8+ | supported | polling plus `SKIP LOCKED` |
|
|
311
|
-
| MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
|
|
312
|
-
| SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
|
|
313
|
-
|
|
314
|
-
Polling is the portability path everywhere. Backend-specific features improve
|
|
315
|
-
latency and throughput but are not correctness requirements.
|
|
316
|
-
|
|
317
|
-
## Data Contract
|
|
324
|
+
### Data Contract
|
|
318
325
|
|
|
319
|
-
Job payloads and persisted return values are stored in JSON columns, so they
|
|
320
|
-
must be JSON round-trippable.
|
|
326
|
+
Job payloads and persisted return values are stored in JSON columns, so they must be JSON round-trippable.
|
|
321
327
|
|
|
322
328
|
- enqueueing args or kwargs that cannot round-trip through JSON fails immediately
|
|
323
329
|
- returning a non-JSON-serializable value marks the job failed instead of
|
|
324
330
|
leaving it claimed forever
|
|
325
331
|
|
|
326
|
-
If you need to pass model instances, files, or custom objects, store them
|
|
327
|
-
elsewhere and pass identifiers or serialized data instead.
|
|
332
|
+
If you need to pass model instances, files, or custom objects, store them elsewhere and pass identifiers or serialized data instead.
|
|
328
333
|
|
|
329
|
-
##
|
|
330
|
-
|
|
331
|
-
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
332
|
-
backend-side validation failures instead of silently dropping work.
|
|
333
|
-
|
|
334
|
-
Common reasons include:
|
|
334
|
+
## Database Support
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
| Backend | Support level | Notes |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
|
|
339
|
+
| MySQL 8+ | supported | polling plus `SKIP LOCKED` |
|
|
340
|
+
| MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
|
|
341
|
+
| SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
|
|
341
342
|
|
|
342
|
-
|
|
343
|
-
from dj_queue.exceptions import EnqueueError
|
|
343
|
+
For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
|
|
344
344
|
|
|
345
|
-
|
|
346
|
-
sync_account.enqueue(account_id, "refresh")
|
|
347
|
-
except EnqueueError as exc:
|
|
348
|
-
handle_enqueue_error(exc)
|
|
349
|
-
```
|
|
345
|
+
Polling is the portability path everywhere. Backend-specific features improve latency and throughput but are not correctness requirements.
|
|
350
346
|
|
|
351
|
-
|
|
352
|
-
inspectable in the queue database.
|
|
347
|
+
For production PostgreSQL operational guidance, see [Postgres Queue Health](#postgres-queue-health).
|
|
353
348
|
|
|
354
349
|
## Recurring Tasks
|
|
355
350
|
|
|
@@ -406,9 +401,10 @@ config.
|
|
|
406
401
|
The scheduler is part of the normal `dj_queue` runtime. You do not run a
|
|
407
402
|
separate recurring service.
|
|
408
403
|
|
|
409
|
-
|
|
404
|
+
Notes:
|
|
410
405
|
|
|
411
406
|
- schedules are cron expressions
|
|
407
|
+
- recurring task keys are scoped per backend alias
|
|
412
408
|
- only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
|
|
413
409
|
- Django admin exposes the same unschedule operation on recurring-task list and detail views
|
|
414
410
|
- multiple schedulers sharing the same recurring config dedupe firing in the database
|
|
@@ -442,6 +438,10 @@ With this configuration:
|
|
|
442
438
|
- later jobs for the same key can block until capacity is released
|
|
443
439
|
- `on_conflict = "discard"` turns the same pattern into singleton-style work
|
|
444
440
|
|
|
441
|
+
Semaphore rows remain shared on the queue database. If you want per-backend
|
|
442
|
+
isolation for a limit, express that in the `concurrency_key` itself rather than
|
|
443
|
+
expecting one semaphore namespace per backend alias.
|
|
444
|
+
|
|
445
445
|
## Queue Operations
|
|
446
446
|
|
|
447
447
|
`QueueInfo` exposes operational queue controls without bypassing the queue
|
|
@@ -464,6 +464,7 @@ orders.clear()
|
|
|
464
464
|
Queue control notes:
|
|
465
465
|
|
|
466
466
|
- pausing a queue stops future claims, not enqueueing or already-claimed work
|
|
467
|
+
- pause rows are scoped per backend alias
|
|
467
468
|
- `clear()` discards ready jobs only
|
|
468
469
|
- pass `backend_alias=` when you want to target a non-default `TASKS` alias
|
|
469
470
|
|
|
@@ -523,6 +524,31 @@ except UndiscardableError:
|
|
|
523
524
|
|
|
524
525
|
Failures stay inspectable until you act on them.
|
|
525
526
|
|
|
527
|
+
## Errors When Enqueuing
|
|
528
|
+
|
|
529
|
+
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
530
|
+
backend-side validation failures instead of silently dropping work.
|
|
531
|
+
|
|
532
|
+
Common reasons include:
|
|
533
|
+
|
|
534
|
+
- args or kwargs are not JSON round-trippable
|
|
535
|
+
- `concurrency_key` is set without `concurrency_limit`
|
|
536
|
+
- `concurrency_key` cannot be resolved from the enqueue arguments
|
|
537
|
+
- `concurrency_key` does not resolve to a non-empty string up to 255 chars
|
|
538
|
+
- `on_conflict` is not `"block"` or `"discard"`
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
from dj_queue.exceptions import EnqueueError
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
sync_account.enqueue(account_id, "refresh")
|
|
545
|
+
except EnqueueError as exc:
|
|
546
|
+
handle_enqueue_error(exc)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Task execution errors are different: they become failed jobs and stay
|
|
550
|
+
inspectable in the queue database.
|
|
551
|
+
|
|
526
552
|
## Multi-Database Setup
|
|
527
553
|
|
|
528
554
|
`dj_queue` can keep queue tables on a dedicated database alias.
|
|
@@ -565,6 +591,28 @@ python manage.py migrate dj_queue --database queue
|
|
|
565
591
|
With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
|
|
566
592
|
database.
|
|
567
593
|
|
|
594
|
+
## Postgres Queue Health
|
|
595
|
+
|
|
596
|
+
Operational and configuration guidance for scaling with `dj_queue` in
|
|
597
|
+
production PostgreSQL deployments, covering dedicated database setup, retention
|
|
598
|
+
policy, and autovacuum tuning.
|
|
599
|
+
|
|
600
|
+
- Use a dedicated queue database via `database_alias`. Keep reporting and
|
|
601
|
+
long-running transactions off the queue database.
|
|
602
|
+
- Keep retention short. Set `preserve_finished_jobs = False` if you do not need
|
|
603
|
+
successful results. Otherwise use bounded `clear_finished_jobs_after`,
|
|
604
|
+
`clear_failed_jobs_after`, and `clear_recurring_executions_after` values.
|
|
605
|
+
- Run `python manage.py dj_queue_prune` regularly for stricter cleanup.
|
|
606
|
+
- Keep `use_skip_locked = True` and `listen_notify = True` unless you have a
|
|
607
|
+
specific reason not to.
|
|
608
|
+
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
609
|
+
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
610
|
+
conservative for queue workloads.
|
|
611
|
+
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
612
|
+
transactions pin dead tuples and delay vacuum.
|
|
613
|
+
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
614
|
+
reaching for partitioning or bulk-ingest paths.
|
|
615
|
+
|
|
568
616
|
## Embedded Server Mode
|
|
569
617
|
|
|
570
618
|
`dj_queue` can run inside an existing server process via embedded async
|
|
@@ -672,9 +720,13 @@ python manage.py dj_queue --config /etc/dj_queue.yml
|
|
|
672
720
|
DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
|
|
673
721
|
```
|
|
674
722
|
|
|
675
|
-
The YAML file
|
|
676
|
-
|
|
677
|
-
|
|
723
|
+
The YAML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
|
|
724
|
+
two shapes:
|
|
725
|
+
|
|
726
|
+
- a flat mapping of option values for the selected backend alias
|
|
727
|
+
- a `backends` mapping keyed by backend alias, where only the selected alias is applied
|
|
728
|
+
|
|
729
|
+
Flat mapping example:
|
|
678
730
|
|
|
679
731
|
```yaml
|
|
680
732
|
mode: async
|
|
@@ -711,8 +763,28 @@ recurring:
|
|
|
711
763
|
description: nightly cleanup
|
|
712
764
|
```
|
|
713
765
|
|
|
714
|
-
|
|
715
|
-
|
|
766
|
+
Multi-backend overlay example:
|
|
767
|
+
|
|
768
|
+
```yaml
|
|
769
|
+
backends:
|
|
770
|
+
default:
|
|
771
|
+
mode: async
|
|
772
|
+
database_alias: default
|
|
773
|
+
workers:
|
|
774
|
+
- queues: ["default", "email*"]
|
|
775
|
+
threads: 8
|
|
776
|
+
processes: 1
|
|
777
|
+
polling_interval: 0.1
|
|
778
|
+
|
|
779
|
+
critical:
|
|
780
|
+
mode: fork
|
|
781
|
+
database_alias: queue
|
|
782
|
+
workers:
|
|
783
|
+
- queues: ["alerts", "critical-review"]
|
|
784
|
+
threads: 2
|
|
785
|
+
processes: 1
|
|
786
|
+
polling_interval: 0.05
|
|
787
|
+
```
|
|
716
788
|
|
|
717
789
|
Environment overrides currently supported by `dj_queue` itself:
|
|
718
790
|
|
|
@@ -777,6 +849,40 @@ such as hook failures, heartbeat failures, notify-watcher failures, and managed
|
|
|
777
849
|
runner crashes. It is not used for exceptions raised by your task code; those
|
|
778
850
|
become failed jobs instead.
|
|
779
851
|
|
|
852
|
+
## Monitoring
|
|
853
|
+
|
|
854
|
+
Queue statistics are available in JSON via `/dj_queue/stats.json` and in
|
|
855
|
+
Prometheus text format via `/dj_queue/metrics`.
|
|
856
|
+
|
|
857
|
+
Include `dj_queue.urls` to expose them:
|
|
858
|
+
|
|
859
|
+
```python
|
|
860
|
+
urlpatterns += [path("dj_queue/", include("dj_queue.urls"))]
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
The `/dj_queue/metrics` endpoint requires the `prometheus` extra:
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
pip install "dj-queue[prometheus]"
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Exported metric families:
|
|
870
|
+
|
|
871
|
+
- `dj_queue_queue_jobs{backend,queue,state}`
|
|
872
|
+
- `dj_queue_queue_paused{backend,queue}`
|
|
873
|
+
- `dj_queue_queue_latency_seconds{backend,queue}`
|
|
874
|
+
- `dj_queue_queue_live_workers{backend,queue}`
|
|
875
|
+
- `dj_queue_runner_processes{backend,status}`
|
|
876
|
+
- `dj_queue_runner_processes_by_kind{backend,kind,status}`
|
|
877
|
+
- `dj_queue_recurring_tasks{backend}`
|
|
878
|
+
- `dj_queue_semaphores{queue_database}`
|
|
879
|
+
- `dj_queue_process_rows{backend}`
|
|
880
|
+
|
|
881
|
+
Both endpoints support bearer token authentication. Set
|
|
882
|
+
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
883
|
+
`Authorization: Bearer <token>`. Leave it unset if you protect these URLs at
|
|
884
|
+
the network or proxy layer.
|
|
885
|
+
|
|
780
886
|
## License
|
|
781
887
|
|
|
782
888
|
MIT
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dj-queue
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: Database-backed task queue backend for Django's django.tasks framework
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Classifier: Development Status :: 4 - Beta
|
|
8
|
-
Classifier: Framework :: Django
|
|
9
|
-
Classifier: Framework :: Django :: 6.0
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
-
Requires-Dist: croniter>=6.2.2
|
|
17
|
-
Requires-Dist: django>=6.0.0
|
|
18
|
-
Requires-Dist: pyyaml>=6.0.3
|
|
19
|
-
Requires-Dist: psycopg>=3.3.3 ; extra == 'postgres'
|
|
20
|
-
Requires-Python: >=3.12
|
|
21
|
-
Project-URL: Homepage, https://github.com/coriocactus/dj_queue
|
|
22
|
-
Project-URL: Repository, https://github.com/coriocactus/dj_queue
|
|
23
|
-
Project-URL: Issues, https://github.com/coriocactus/dj_queue/issues
|
|
24
|
-
Provides-Extra: postgres
|
|
25
|
-
Description-Content-Type: text/markdown
|
|
26
|
-
|
|
27
1
|
# dj_queue
|
|
28
2
|
|
|
29
3
|
[](https://github.com/coriocactus/dj_queue/actions/workflows/ci.yml)
|
|
@@ -43,7 +17,7 @@ It keeps the queue, live execution state, runtime metadata, and task results in
|
|
|
43
17
|
- immediate, scheduled, recurring, and concurrency-limited work
|
|
44
18
|
|
|
45
19
|
`dj_queue` is inspired by Rails' [Solid Queue](https://github.com/rails/solid_queue),
|
|
46
|
-
|
|
20
|
+
shaped to fit Django's [task backend API](https://docs.djangoproject.com/en/6.0/topics/tasks/).
|
|
47
21
|
|
|
48
22
|
## Why dj_queue
|
|
49
23
|
|
|
@@ -71,20 +45,13 @@ Install the package:
|
|
|
71
45
|
pip install dj-queue
|
|
72
46
|
```
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
database adapter for you:
|
|
48
|
+
Optional extras:
|
|
76
49
|
|
|
77
50
|
```bash
|
|
78
|
-
pip install "dj-queue[postgres]"
|
|
51
|
+
pip install "dj-queue[postgres]" # psycopg for PostgreSQL + LISTEN/NOTIFY
|
|
52
|
+
pip install "dj-queue[prometheus]" # prometheus_client for /dj_queue/metrics
|
|
79
53
|
```
|
|
80
54
|
|
|
81
|
-
Notes:
|
|
82
|
-
|
|
83
|
-
- `postgres` installs `psycopg`, which Django's PostgreSQL backend and
|
|
84
|
-
`dj_queue`'s optional `LISTEN/NOTIFY` wakeups use
|
|
85
|
-
- for MySQL or MariaDB, install and configure a Django-compatible driver in
|
|
86
|
-
your application following Django's database docs
|
|
87
|
-
|
|
88
55
|
Add `dj_queue` to `INSTALLED_APPS`, register the router, and point Django's task
|
|
89
56
|
backend at `DjQueueBackend`:
|
|
90
57
|
|
|
@@ -164,12 +131,10 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
164
131
|
- queue, process, recurring-task, and semaphore overview
|
|
165
132
|
- backend-aware dashboard and raw changelists
|
|
166
133
|
- queue controls: pause, resume, clear ready
|
|
167
|
-
- job
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
- failed-job actions: retry and discard from list and detail views
|
|
134
|
+
- job actions: enqueue a fresh copy of any stored job
|
|
135
|
+
- failed jobs: retry and discard from list and detail views
|
|
136
|
+
- unschedule dynamic recurring tasks
|
|
171
137
|
- queue drill-down pages for state-specific inspection
|
|
172
|
-
- queue drill-down actions: discard ready, scheduled, and blocked jobs; retry or discard failed jobs; enqueue finished jobs again
|
|
173
138
|
|
|
174
139
|
**Dashboard overview**
|
|
175
140
|
|
|
@@ -328,54 +293,30 @@ Runners heartbeat into the queue database. If claimed work is left behind,
|
|
|
328
293
|
Use `python manage.py dj_queue_health` to check whether any fresh runtime
|
|
329
294
|
process rows exist for a backend.
|
|
330
295
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
| Backend | Support level | Notes |
|
|
334
|
-
|---|---|---|
|
|
335
|
-
| PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
|
|
336
|
-
| MySQL 8+ | supported | polling plus `SKIP LOCKED` |
|
|
337
|
-
| MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
|
|
338
|
-
| SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
|
|
339
|
-
|
|
340
|
-
Polling is the portability path everywhere. Backend-specific features improve
|
|
341
|
-
latency and throughput but are not correctness requirements.
|
|
342
|
-
|
|
343
|
-
## Data Contract
|
|
296
|
+
### Data Contract
|
|
344
297
|
|
|
345
|
-
Job payloads and persisted return values are stored in JSON columns, so they
|
|
346
|
-
must be JSON round-trippable.
|
|
298
|
+
Job payloads and persisted return values are stored in JSON columns, so they must be JSON round-trippable.
|
|
347
299
|
|
|
348
300
|
- enqueueing args or kwargs that cannot round-trip through JSON fails immediately
|
|
349
301
|
- returning a non-JSON-serializable value marks the job failed instead of
|
|
350
302
|
leaving it claimed forever
|
|
351
303
|
|
|
352
|
-
If you need to pass model instances, files, or custom objects, store them
|
|
353
|
-
elsewhere and pass identifiers or serialized data instead.
|
|
304
|
+
If you need to pass model instances, files, or custom objects, store them elsewhere and pass identifiers or serialized data instead.
|
|
354
305
|
|
|
355
|
-
##
|
|
356
|
-
|
|
357
|
-
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
358
|
-
backend-side validation failures instead of silently dropping work.
|
|
359
|
-
|
|
360
|
-
Common reasons include:
|
|
306
|
+
## Database Support
|
|
361
307
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
|
|
308
|
+
| Backend | Support level | Notes |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
|
|
311
|
+
| MySQL 8+ | supported | polling plus `SKIP LOCKED` |
|
|
312
|
+
| MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
|
|
313
|
+
| SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
|
|
367
314
|
|
|
368
|
-
|
|
369
|
-
from dj_queue.exceptions import EnqueueError
|
|
315
|
+
For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
|
|
370
316
|
|
|
371
|
-
|
|
372
|
-
sync_account.enqueue(account_id, "refresh")
|
|
373
|
-
except EnqueueError as exc:
|
|
374
|
-
handle_enqueue_error(exc)
|
|
375
|
-
```
|
|
317
|
+
Polling is the portability path everywhere. Backend-specific features improve latency and throughput but are not correctness requirements.
|
|
376
318
|
|
|
377
|
-
|
|
378
|
-
inspectable in the queue database.
|
|
319
|
+
For production PostgreSQL operational guidance, see [Postgres Queue Health](#postgres-queue-health).
|
|
379
320
|
|
|
380
321
|
## Recurring Tasks
|
|
381
322
|
|
|
@@ -432,9 +373,10 @@ config.
|
|
|
432
373
|
The scheduler is part of the normal `dj_queue` runtime. You do not run a
|
|
433
374
|
separate recurring service.
|
|
434
375
|
|
|
435
|
-
|
|
376
|
+
Notes:
|
|
436
377
|
|
|
437
378
|
- schedules are cron expressions
|
|
379
|
+
- recurring task keys are scoped per backend alias
|
|
438
380
|
- only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
|
|
439
381
|
- Django admin exposes the same unschedule operation on recurring-task list and detail views
|
|
440
382
|
- multiple schedulers sharing the same recurring config dedupe firing in the database
|
|
@@ -468,6 +410,10 @@ With this configuration:
|
|
|
468
410
|
- later jobs for the same key can block until capacity is released
|
|
469
411
|
- `on_conflict = "discard"` turns the same pattern into singleton-style work
|
|
470
412
|
|
|
413
|
+
Semaphore rows remain shared on the queue database. If you want per-backend
|
|
414
|
+
isolation for a limit, express that in the `concurrency_key` itself rather than
|
|
415
|
+
expecting one semaphore namespace per backend alias.
|
|
416
|
+
|
|
471
417
|
## Queue Operations
|
|
472
418
|
|
|
473
419
|
`QueueInfo` exposes operational queue controls without bypassing the queue
|
|
@@ -490,6 +436,7 @@ orders.clear()
|
|
|
490
436
|
Queue control notes:
|
|
491
437
|
|
|
492
438
|
- pausing a queue stops future claims, not enqueueing or already-claimed work
|
|
439
|
+
- pause rows are scoped per backend alias
|
|
493
440
|
- `clear()` discards ready jobs only
|
|
494
441
|
- pass `backend_alias=` when you want to target a non-default `TASKS` alias
|
|
495
442
|
|
|
@@ -549,6 +496,31 @@ except UndiscardableError:
|
|
|
549
496
|
|
|
550
497
|
Failures stay inspectable until you act on them.
|
|
551
498
|
|
|
499
|
+
## Errors When Enqueuing
|
|
500
|
+
|
|
501
|
+
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
502
|
+
backend-side validation failures instead of silently dropping work.
|
|
503
|
+
|
|
504
|
+
Common reasons include:
|
|
505
|
+
|
|
506
|
+
- args or kwargs are not JSON round-trippable
|
|
507
|
+
- `concurrency_key` is set without `concurrency_limit`
|
|
508
|
+
- `concurrency_key` cannot be resolved from the enqueue arguments
|
|
509
|
+
- `concurrency_key` does not resolve to a non-empty string up to 255 chars
|
|
510
|
+
- `on_conflict` is not `"block"` or `"discard"`
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
from dj_queue.exceptions import EnqueueError
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
sync_account.enqueue(account_id, "refresh")
|
|
517
|
+
except EnqueueError as exc:
|
|
518
|
+
handle_enqueue_error(exc)
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Task execution errors are different: they become failed jobs and stay
|
|
522
|
+
inspectable in the queue database.
|
|
523
|
+
|
|
552
524
|
## Multi-Database Setup
|
|
553
525
|
|
|
554
526
|
`dj_queue` can keep queue tables on a dedicated database alias.
|
|
@@ -591,6 +563,28 @@ python manage.py migrate dj_queue --database queue
|
|
|
591
563
|
With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
|
|
592
564
|
database.
|
|
593
565
|
|
|
566
|
+
## Postgres Queue Health
|
|
567
|
+
|
|
568
|
+
Operational and configuration guidance for scaling with `dj_queue` in
|
|
569
|
+
production PostgreSQL deployments, covering dedicated database setup, retention
|
|
570
|
+
policy, and autovacuum tuning.
|
|
571
|
+
|
|
572
|
+
- Use a dedicated queue database via `database_alias`. Keep reporting and
|
|
573
|
+
long-running transactions off the queue database.
|
|
574
|
+
- Keep retention short. Set `preserve_finished_jobs = False` if you do not need
|
|
575
|
+
successful results. Otherwise use bounded `clear_finished_jobs_after`,
|
|
576
|
+
`clear_failed_jobs_after`, and `clear_recurring_executions_after` values.
|
|
577
|
+
- Run `python manage.py dj_queue_prune` regularly for stricter cleanup.
|
|
578
|
+
- Keep `use_skip_locked = True` and `listen_notify = True` unless you have a
|
|
579
|
+
specific reason not to.
|
|
580
|
+
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
581
|
+
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
582
|
+
conservative for queue workloads.
|
|
583
|
+
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
584
|
+
transactions pin dead tuples and delay vacuum.
|
|
585
|
+
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
586
|
+
reaching for partitioning or bulk-ingest paths.
|
|
587
|
+
|
|
594
588
|
## Embedded Server Mode
|
|
595
589
|
|
|
596
590
|
`dj_queue` can run inside an existing server process via embedded async
|
|
@@ -698,9 +692,13 @@ python manage.py dj_queue --config /etc/dj_queue.yml
|
|
|
698
692
|
DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
|
|
699
693
|
```
|
|
700
694
|
|
|
701
|
-
The YAML file
|
|
702
|
-
|
|
703
|
-
|
|
695
|
+
The YAML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
|
|
696
|
+
two shapes:
|
|
697
|
+
|
|
698
|
+
- a flat mapping of option values for the selected backend alias
|
|
699
|
+
- a `backends` mapping keyed by backend alias, where only the selected alias is applied
|
|
700
|
+
|
|
701
|
+
Flat mapping example:
|
|
704
702
|
|
|
705
703
|
```yaml
|
|
706
704
|
mode: async
|
|
@@ -737,8 +735,28 @@ recurring:
|
|
|
737
735
|
description: nightly cleanup
|
|
738
736
|
```
|
|
739
737
|
|
|
740
|
-
|
|
741
|
-
|
|
738
|
+
Multi-backend overlay example:
|
|
739
|
+
|
|
740
|
+
```yaml
|
|
741
|
+
backends:
|
|
742
|
+
default:
|
|
743
|
+
mode: async
|
|
744
|
+
database_alias: default
|
|
745
|
+
workers:
|
|
746
|
+
- queues: ["default", "email*"]
|
|
747
|
+
threads: 8
|
|
748
|
+
processes: 1
|
|
749
|
+
polling_interval: 0.1
|
|
750
|
+
|
|
751
|
+
critical:
|
|
752
|
+
mode: fork
|
|
753
|
+
database_alias: queue
|
|
754
|
+
workers:
|
|
755
|
+
- queues: ["alerts", "critical-review"]
|
|
756
|
+
threads: 2
|
|
757
|
+
processes: 1
|
|
758
|
+
polling_interval: 0.05
|
|
759
|
+
```
|
|
742
760
|
|
|
743
761
|
Environment overrides currently supported by `dj_queue` itself:
|
|
744
762
|
|
|
@@ -803,6 +821,40 @@ such as hook failures, heartbeat failures, notify-watcher failures, and managed
|
|
|
803
821
|
runner crashes. It is not used for exceptions raised by your task code; those
|
|
804
822
|
become failed jobs instead.
|
|
805
823
|
|
|
824
|
+
## Monitoring
|
|
825
|
+
|
|
826
|
+
Queue statistics are available in JSON via `/dj_queue/stats.json` and in
|
|
827
|
+
Prometheus text format via `/dj_queue/metrics`.
|
|
828
|
+
|
|
829
|
+
Include `dj_queue.urls` to expose them:
|
|
830
|
+
|
|
831
|
+
```python
|
|
832
|
+
urlpatterns += [path("dj_queue/", include("dj_queue.urls"))]
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
The `/dj_queue/metrics` endpoint requires the `prometheus` extra:
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
pip install "dj-queue[prometheus]"
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Exported metric families:
|
|
842
|
+
|
|
843
|
+
- `dj_queue_queue_jobs{backend,queue,state}`
|
|
844
|
+
- `dj_queue_queue_paused{backend,queue}`
|
|
845
|
+
- `dj_queue_queue_latency_seconds{backend,queue}`
|
|
846
|
+
- `dj_queue_queue_live_workers{backend,queue}`
|
|
847
|
+
- `dj_queue_runner_processes{backend,status}`
|
|
848
|
+
- `dj_queue_runner_processes_by_kind{backend,kind,status}`
|
|
849
|
+
- `dj_queue_recurring_tasks{backend}`
|
|
850
|
+
- `dj_queue_semaphores{queue_database}`
|
|
851
|
+
- `dj_queue_process_rows{backend}`
|
|
852
|
+
|
|
853
|
+
Both endpoints support bearer token authentication. Set
|
|
854
|
+
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
855
|
+
`Authorization: Bearer <token>`. Leave it unset if you protect these URLs at
|
|
856
|
+
the network or proxy layer.
|
|
857
|
+
|
|
806
858
|
## License
|
|
807
859
|
|
|
808
860
|
MIT
|