dj-queue 0.3.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.3.0 → dj_queue-0.5.0}/PKG-INFO +292 -42
- {dj_queue-0.3.0 → dj_queue-0.5.0}/README.md +288 -40
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/admin.py +178 -24
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/api.py +55 -8
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/backend.py +4 -4
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/config.py +45 -5
- dj_queue-0.5.0/dj_queue/contrib/prometheus.py +129 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/dashboard.py +71 -430
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/management/commands/dj_queue_health.py +8 -1
- dj_queue-0.5.0/dj_queue/management/commands/dj_queue_prune.py +44 -0
- 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.3.0 → dj_queue-0.5.0}/dj_queue/models/jobs.py +11 -11
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/models/recurring.py +13 -4
- {dj_queue-0.3.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.5.0/dj_queue/operations/cleanup.py +112 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/operations/concurrency.py +33 -11
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/operations/jobs.py +123 -37
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/operations/recurring.py +12 -3
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/base.py +5 -1
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/notify.py +2 -2
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/scheduler.py +35 -13
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/supervisor.py +20 -5
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +1 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +4 -2
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/change_form.html +17 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/dashboard.html +4 -48
- dj_queue-0.5.0/dj_queue/templates/admin/dj_queue/includes/fieldset.html +43 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +11 -2
- dj_queue-0.5.0/dj_queue/templatetags/__init__.py +0 -0
- dj_queue-0.5.0/dj_queue/templatetags/dj_queue_admin.py +45 -0
- 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.3.0 → dj_queue-0.5.0}/pyproject.toml +4 -2
- dj_queue-0.3.0/dj_queue/management/commands/dj_queue_prune.py +0 -22
- dj_queue-0.3.0/dj_queue/operations/cleanup.py +0 -37
- {dj_queue-0.3.0 → dj_queue-0.5.0}/LICENSE +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/db.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/log.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.3.0 → dj_queue-0.5.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Database-backed task queue backend for Django
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -17,11 +17,13 @@ Requires-Dist: croniter>=6.2.2
|
|
|
17
17
|
Requires-Dist: django>=6.0.0
|
|
18
18
|
Requires-Dist: pyyaml>=6.0.3
|
|
19
19
|
Requires-Dist: psycopg>=3.3.3 ; extra == 'postgres'
|
|
20
|
+
Requires-Dist: prometheus-client>=0.4.0 ; extra == 'prometheus'
|
|
20
21
|
Requires-Python: >=3.12
|
|
21
22
|
Project-URL: Homepage, https://github.com/coriocactus/dj_queue
|
|
22
23
|
Project-URL: Repository, https://github.com/coriocactus/dj_queue
|
|
23
24
|
Project-URL: Issues, https://github.com/coriocactus/dj_queue/issues
|
|
24
25
|
Provides-Extra: postgres
|
|
26
|
+
Provides-Extra: prometheus
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
26
28
|
|
|
27
29
|
# dj_queue
|
|
@@ -43,7 +45,7 @@ It keeps the queue, live execution state, runtime metadata, and task results in
|
|
|
43
45
|
- immediate, scheduled, recurring, and concurrency-limited work
|
|
44
46
|
|
|
45
47
|
`dj_queue` is inspired by Rails' [Solid Queue](https://github.com/rails/solid_queue),
|
|
46
|
-
|
|
48
|
+
shaped to fit Django's [task backend API](https://docs.djangoproject.com/en/6.0/topics/tasks/).
|
|
47
49
|
|
|
48
50
|
## Why dj_queue
|
|
49
51
|
|
|
@@ -71,20 +73,13 @@ Install the package:
|
|
|
71
73
|
pip install dj-queue
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
database adapter for you:
|
|
76
|
+
Optional extras:
|
|
76
77
|
|
|
77
78
|
```bash
|
|
78
|
-
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
|
|
79
81
|
```
|
|
80
82
|
|
|
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
83
|
Add `dj_queue` to `INSTALLED_APPS`, register the router, and point Django's task
|
|
89
84
|
backend at `DjQueueBackend`:
|
|
90
85
|
|
|
@@ -164,9 +159,9 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
164
159
|
- queue, process, recurring-task, and semaphore overview
|
|
165
160
|
- backend-aware dashboard and raw changelists
|
|
166
161
|
- queue controls: pause, resume, clear ready
|
|
167
|
-
- job
|
|
168
|
-
-
|
|
169
|
-
-
|
|
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
|
|
170
165
|
- queue drill-down pages for state-specific inspection
|
|
171
166
|
|
|
172
167
|
**Dashboard overview**
|
|
@@ -217,8 +212,9 @@ results = process_item.get_backend().enqueue_all(
|
|
|
217
212
|
|
|
218
213
|
### Enqueue after commit
|
|
219
214
|
|
|
220
|
-
`enqueue()` writes immediately
|
|
221
|
-
the current transaction, use
|
|
215
|
+
`enqueue()` writes immediately and returns a real persisted task result ID. If a
|
|
216
|
+
task depends on rows that are still inside the current transaction, use
|
|
217
|
+
`enqueue_on_commit()`:
|
|
222
218
|
|
|
223
219
|
```python
|
|
224
220
|
from django.db import transaction
|
|
@@ -230,6 +226,10 @@ with transaction.atomic():
|
|
|
230
226
|
enqueue_on_commit(send_receipt, order.id)
|
|
231
227
|
```
|
|
232
228
|
|
|
229
|
+
`dj_queue` does not defer inserts implicitly or return placeholder result IDs for
|
|
230
|
+
uncommitted work. Use the helper above or `transaction.on_commit()` directly
|
|
231
|
+
when the job must not exist before commit.
|
|
232
|
+
|
|
233
233
|
### Examples
|
|
234
234
|
|
|
235
235
|
The repository ships real runnable examples in `examples/`.
|
|
@@ -274,14 +274,18 @@ python manage.py dj_queue --skip-recurring
|
|
|
274
274
|
Mode and topology notes:
|
|
275
275
|
|
|
276
276
|
- `fork` is the default standalone mode
|
|
277
|
-
- `async` runs supervised actors in threads inside one process
|
|
277
|
+
- `async` is also supported as a standalone mode and runs supervised actors in threads inside one process
|
|
278
278
|
- `--only-work` starts workers without dispatchers or scheduler
|
|
279
279
|
- `--only-dispatch` starts dispatchers without workers or scheduler
|
|
280
280
|
- `--skip-recurring` starts without the scheduler
|
|
281
281
|
|
|
282
282
|
`fork` runs each worker, dispatcher, and scheduler as a separate OS process.
|
|
283
283
|
`async` runs them as threads in one process, i.e., lower memory, less isolation.
|
|
284
|
-
Default is `fork`. Use `async`
|
|
284
|
+
Default is `fork`. Use standalone `async` when you want one-process supervision
|
|
285
|
+
with lower memory use and less isolation, or embedded `async` when `dj_queue`
|
|
286
|
+
should live inside an ASGI or Gunicorn server process.
|
|
287
|
+
|
|
288
|
+
In `async` mode, worker `processes > 1` is ignored and normalized to `1`.
|
|
285
289
|
|
|
286
290
|
### Claiming order
|
|
287
291
|
|
|
@@ -294,6 +298,39 @@ For example, a worker configured with `queues: ["email", "default"]` will
|
|
|
294
298
|
prefer ready work from `email` before `default`, even if `default` contains
|
|
295
299
|
higher-priority rows.
|
|
296
300
|
|
|
301
|
+
If you combine queue order with priorities, queue selector order still wins
|
|
302
|
+
across queues. Prefer one primary scheduling mechanism per worker when you can.
|
|
303
|
+
|
|
304
|
+
### Signals and recovery
|
|
305
|
+
|
|
306
|
+
In standalone mode, both `fork` and `async` `python manage.py dj_queue`
|
|
307
|
+
supervisors own runtime signal handling:
|
|
308
|
+
|
|
309
|
+
- `SIGTERM` and `SIGINT` request graceful shutdown
|
|
310
|
+
- `SIGQUIT` takes the immediate hard-exit path
|
|
311
|
+
- `shutdown_timeout` controls how long the runtime waits for in-flight work to drain
|
|
312
|
+
- `supervisor_pidfile` can prevent duplicate standalone supervisors on one host
|
|
313
|
+
|
|
314
|
+
Runners heartbeat into the queue database. If claimed work is left behind,
|
|
315
|
+
`dj_queue` preserves it as failed work that operators can inspect and retry:
|
|
316
|
+
|
|
317
|
+
- `ProcessExitError`: a supervised runner exited unexpectedly
|
|
318
|
+
- `ProcessPrunedError`: a runner heartbeat expired and the process was pruned
|
|
319
|
+
- `ProcessMissingError`: claimed work was found without its registered process
|
|
320
|
+
|
|
321
|
+
Use `python manage.py dj_queue_health` to check whether any fresh runtime
|
|
322
|
+
process rows exist for a backend.
|
|
323
|
+
|
|
324
|
+
### Data Contract
|
|
325
|
+
|
|
326
|
+
Job payloads and persisted return values are stored in JSON columns, so they must be JSON round-trippable.
|
|
327
|
+
|
|
328
|
+
- enqueueing args or kwargs that cannot round-trip through JSON fails immediately
|
|
329
|
+
- returning a non-JSON-serializable value marks the job failed instead of
|
|
330
|
+
leaving it claimed forever
|
|
331
|
+
|
|
332
|
+
If you need to pass model instances, files, or custom objects, store them elsewhere and pass identifiers or serialized data instead.
|
|
333
|
+
|
|
297
334
|
## Database Support
|
|
298
335
|
|
|
299
336
|
| Backend | Support level | Notes |
|
|
@@ -303,20 +340,11 @@ higher-priority rows.
|
|
|
303
340
|
| MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
|
|
304
341
|
| SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
|
|
305
342
|
|
|
306
|
-
|
|
307
|
-
latency and throughput but are not correctness requirements.
|
|
308
|
-
|
|
309
|
-
## Data Contract
|
|
310
|
-
|
|
311
|
-
Job payloads and persisted return values are stored in JSON columns, so they
|
|
312
|
-
must be JSON round-trippable.
|
|
343
|
+
For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
|
|
313
344
|
|
|
314
|
-
-
|
|
315
|
-
- returning a non-JSON-serializable value marks the job failed instead of
|
|
316
|
-
leaving it claimed forever
|
|
345
|
+
Polling is the portability path everywhere. Backend-specific features improve latency and throughput but are not correctness requirements.
|
|
317
346
|
|
|
318
|
-
|
|
319
|
-
elsewhere and pass identifiers or serialized data instead.
|
|
347
|
+
For production PostgreSQL operational guidance, see [Postgres Queue Health](#postgres-queue-health).
|
|
320
348
|
|
|
321
349
|
## Recurring Tasks
|
|
322
350
|
|
|
@@ -373,6 +401,17 @@ config.
|
|
|
373
401
|
The scheduler is part of the normal `dj_queue` runtime. You do not run a
|
|
374
402
|
separate recurring service.
|
|
375
403
|
|
|
404
|
+
Notes:
|
|
405
|
+
|
|
406
|
+
- schedules are cron expressions
|
|
407
|
+
- recurring task keys are scoped per backend alias
|
|
408
|
+
- only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
|
|
409
|
+
- Django admin exposes the same unschedule operation on recurring-task list and detail views
|
|
410
|
+
- multiple schedulers sharing the same recurring config dedupe firing in the database
|
|
411
|
+
- finished-job cleanup runs as internal scheduler maintenance when `preserve_finished_jobs=True` and `clear_finished_jobs_after` is set
|
|
412
|
+
- failed-job cleanup can run as internal scheduler maintenance when `clear_failed_jobs_after` is set
|
|
413
|
+
- recurring execution reservation cleanup can run as internal scheduler maintenance when `clear_recurring_executions_after` is set
|
|
414
|
+
|
|
376
415
|
## Concurrency Controls
|
|
377
416
|
|
|
378
417
|
Tasks can opt into database-backed concurrency limits.
|
|
@@ -399,6 +438,10 @@ With this configuration:
|
|
|
399
438
|
- later jobs for the same key can block until capacity is released
|
|
400
439
|
- `on_conflict = "discard"` turns the same pattern into singleton-style work
|
|
401
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
|
+
|
|
402
445
|
## Queue Operations
|
|
403
446
|
|
|
404
447
|
`QueueInfo` exposes operational queue controls without bypassing the queue
|
|
@@ -418,15 +461,32 @@ orders.resume()
|
|
|
418
461
|
orders.clear()
|
|
419
462
|
```
|
|
420
463
|
|
|
464
|
+
Queue control notes:
|
|
465
|
+
|
|
466
|
+
- pausing a queue stops future claims, not enqueueing or already-claimed work
|
|
467
|
+
- pause rows are scoped per backend alias
|
|
468
|
+
- `clear()` discards ready jobs only
|
|
469
|
+
- pass `backend_alias=` when you want to target a non-default `TASKS` alias
|
|
470
|
+
|
|
421
471
|
Operational commands:
|
|
422
472
|
|
|
423
473
|
```bash
|
|
424
474
|
python manage.py dj_queue_health
|
|
425
475
|
python manage.py dj_queue_health --max-age 120
|
|
426
476
|
python manage.py dj_queue_prune --older-than 86400
|
|
477
|
+
python manage.py dj_queue_prune --failed-older-than 604800
|
|
478
|
+
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
427
479
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
480
|
+
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
428
481
|
```
|
|
429
482
|
|
|
483
|
+
The runtime, health, and prune commands all accept `--backend` to target a
|
|
484
|
+
non-default backend alias.
|
|
485
|
+
|
|
486
|
+
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
487
|
+
task import path, while `--task-key` filters recurring execution cleanup by
|
|
488
|
+
recurring task key.
|
|
489
|
+
|
|
430
490
|
## Failed Jobs
|
|
431
491
|
|
|
432
492
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
@@ -443,8 +503,52 @@ retry_failed_job(job_id)
|
|
|
443
503
|
discard_failed_job(job_id)
|
|
444
504
|
```
|
|
445
505
|
|
|
506
|
+
Model helpers are available too:
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
from dj_queue.exceptions import UndiscardableError
|
|
510
|
+
from dj_queue.models import ClaimedExecution, FailedExecution
|
|
511
|
+
|
|
512
|
+
failed = FailedExecution.objects.get(job_id=job_id)
|
|
513
|
+
failed.retry()
|
|
514
|
+
failed.discard()
|
|
515
|
+
|
|
516
|
+
FailedExecution.retry_all(FailedExecution.objects.order_by("job_id"))
|
|
517
|
+
FailedExecution.discard_all_in_batches()
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
ClaimedExecution.discard_all_in_batches()
|
|
521
|
+
except UndiscardableError:
|
|
522
|
+
pass
|
|
523
|
+
```
|
|
524
|
+
|
|
446
525
|
Failures stay inspectable until you act on them.
|
|
447
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
|
+
|
|
448
552
|
## Multi-Database Setup
|
|
449
553
|
|
|
450
554
|
`dj_queue` can keep queue tables on a dedicated database alias.
|
|
@@ -487,6 +591,28 @@ python manage.py migrate dj_queue --database queue
|
|
|
487
591
|
With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
|
|
488
592
|
database.
|
|
489
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
|
+
|
|
490
616
|
## Embedded Server Mode
|
|
491
617
|
|
|
492
618
|
`dj_queue` can run inside an existing server process via embedded async
|
|
@@ -557,12 +683,19 @@ Start with these options:
|
|
|
557
683
|
- `dispatchers`: scheduled promotion and concurrency maintenance settings
|
|
558
684
|
- `scheduler`: dynamic recurring polling settings
|
|
559
685
|
- `database_alias`: database alias for queue tables and runtime activity
|
|
560
|
-
- `preserve_finished_jobs` and `clear_finished_jobs_after`: result retention and cleanup
|
|
686
|
+
- `preserve_finished_jobs` and `clear_finished_jobs_after`: successful result retention and cleanup
|
|
687
|
+
- `clear_failed_jobs_after`: optional failed-job retention window
|
|
688
|
+
- `clear_recurring_executions_after`: optional recurring reservation retention window
|
|
561
689
|
|
|
562
|
-
Additional operational tuning is available when needed
|
|
563
|
-
|
|
564
|
-
`
|
|
565
|
-
`
|
|
690
|
+
Additional operational tuning is available when needed:
|
|
691
|
+
|
|
692
|
+
- `use_skip_locked`: use `SKIP LOCKED` when the active backend supports it
|
|
693
|
+
- `listen_notify`: PostgreSQL-only worker wakeup optimization layered on top of polling
|
|
694
|
+
- `silence_polling`: suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger
|
|
695
|
+
- `process_heartbeat_interval` and `process_alive_threshold`: process liveness reporting and stale-runner detection
|
|
696
|
+
- `shutdown_timeout`: graceful drain window before standalone shutdown gives up on waiting
|
|
697
|
+
- `supervisor_pidfile`: optional pidfile guard for standalone supervisors
|
|
698
|
+
- `on_thread_error`: dotted callback path for runtime infrastructure exceptions
|
|
566
699
|
|
|
567
700
|
On PostgreSQL, `listen_notify` uses the same Django PostgreSQL driver
|
|
568
701
|
configuration as the main database connection. Install a compatible driver in
|
|
@@ -587,15 +720,21 @@ python manage.py dj_queue --config /etc/dj_queue.yml
|
|
|
587
720
|
DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
|
|
588
721
|
```
|
|
589
722
|
|
|
590
|
-
The YAML file
|
|
591
|
-
|
|
592
|
-
|
|
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:
|
|
593
730
|
|
|
594
731
|
```yaml
|
|
595
732
|
mode: async
|
|
596
733
|
database_alias: queue
|
|
597
734
|
preserve_finished_jobs: true
|
|
598
735
|
clear_finished_jobs_after: 86400
|
|
736
|
+
clear_failed_jobs_after: null
|
|
737
|
+
clear_recurring_executions_after: null
|
|
599
738
|
listen_notify: true
|
|
600
739
|
silence_polling: true
|
|
601
740
|
|
|
@@ -624,8 +763,28 @@ recurring:
|
|
|
624
763
|
description: nightly cleanup
|
|
625
764
|
```
|
|
626
765
|
|
|
627
|
-
|
|
628
|
-
|
|
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
|
+
```
|
|
629
788
|
|
|
630
789
|
Environment overrides currently supported by `dj_queue` itself:
|
|
631
790
|
|
|
@@ -633,6 +792,97 @@ Environment overrides currently supported by `dj_queue` itself:
|
|
|
633
792
|
- `DJ_QUEUE_MODE`
|
|
634
793
|
- `DJ_QUEUE_SKIP_RECURRING`
|
|
635
794
|
|
|
795
|
+
## Lifecycle Hooks
|
|
796
|
+
|
|
797
|
+
Register hooks before starting the runtime, typically during Django startup.
|
|
798
|
+
Each callback receives the live supervisor or runner instance.
|
|
799
|
+
|
|
800
|
+
```python
|
|
801
|
+
from dj_queue.hooks import on_start, on_worker_start, register_hook
|
|
802
|
+
|
|
803
|
+
@on_start
|
|
804
|
+
def supervisor_started(process):
|
|
805
|
+
print(process.name)
|
|
806
|
+
|
|
807
|
+
@on_worker_start
|
|
808
|
+
def worker_started(process):
|
|
809
|
+
print(process.metadata)
|
|
810
|
+
|
|
811
|
+
@register_hook("scheduler.exit")
|
|
812
|
+
def scheduler_exited(process):
|
|
813
|
+
print(process.name)
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Available hook helpers:
|
|
817
|
+
|
|
818
|
+
- supervisor: `on_start`, `on_stop`, `on_exit`
|
|
819
|
+
- worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
|
|
820
|
+
- dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
|
|
821
|
+
- scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
|
|
822
|
+
- generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
|
|
823
|
+
|
|
824
|
+
Hook notes:
|
|
825
|
+
|
|
826
|
+
- hooks fire in registration order
|
|
827
|
+
- hook failures do not block later hooks
|
|
828
|
+
- hook failures are isolated and routed through `on_thread_error`
|
|
829
|
+
|
|
830
|
+
### Runtime infrastructure errors
|
|
831
|
+
|
|
832
|
+
Set `on_thread_error` to a dotted callable path when you want custom handling
|
|
833
|
+
for queue-runtime exceptions:
|
|
834
|
+
|
|
835
|
+
```python
|
|
836
|
+
TASKS = {
|
|
837
|
+
"default": {
|
|
838
|
+
"BACKEND": "dj_queue.backend.DjQueueBackend",
|
|
839
|
+
"QUEUES": [],
|
|
840
|
+
"OPTIONS": {
|
|
841
|
+
"on_thread_error": "myapp.queue.report_runtime_error",
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
The callback receives the raised exception object for background runtime issues
|
|
848
|
+
such as hook failures, heartbeat failures, notify-watcher failures, and managed
|
|
849
|
+
runner crashes. It is not used for exceptions raised by your task code; those
|
|
850
|
+
become failed jobs instead.
|
|
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
|
+
|
|
636
886
|
## License
|
|
637
887
|
|
|
638
888
|
MIT
|