dj-queue 0.12.0__tar.gz → 0.13.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.12.0 → dj_queue-0.13.0}/PKG-INFO +16 -4
- {dj_queue-0.12.0 → dj_queue-0.13.0}/README.md +15 -3
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/admin.py +6 -2
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/api.py +2 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/config.py +19 -11
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/db.py +10 -1
- dj_queue-0.13.0/dj_queue/management/commands/dj_queue_postgres_autovacuum.py +26 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/metrics.py +59 -0
- dj_queue-0.13.0/dj_queue/migrations/0013_failedexecution_retry_at_and_more.py +21 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/models/jobs.py +5 -1
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/observability.py +333 -7
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/concurrency.py +165 -155
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/jobs.py +224 -80
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/queue_state.py +15 -22
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/base.py +13 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/dispatcher.py +11 -2
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/worker.py +3 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/common.py +67 -21
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/mysql.py +13 -1
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/postgres.py +135 -1
- {dj_queue-0.12.0 → dj_queue-0.13.0}/pyproject.toml +1 -1
- {dj_queue-0.12.0 → dj_queue-0.13.0}/LICENSE +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/backend.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/contrib/prometheus.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/cron.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/dashboard.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/dashboard_actions.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/log.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0011_remove_blockedexecution_djq_bl_b_conc_idx_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/0012_job_djq_jobs_b_queue_id_idx_job_djq_jobs_b_conc_idx_and_more.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/_helpers.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/_insert.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/queues.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/queue_selectors.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/connection_budget.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/runtime/topology.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/sqlite.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/sql/state.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/task_results.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/urls.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/views.py +0 -0
- {dj_queue-0.12.0 → dj_queue-0.13.0}/dj_queue/wakeup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -517,9 +517,10 @@ python manage.py dj_queue_prune --failed-older-than 604800
|
|
|
517
517
|
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
518
518
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
519
519
|
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
520
|
+
python manage.py dj_queue_postgres_autovacuum
|
|
520
521
|
```
|
|
521
522
|
|
|
522
|
-
The health and
|
|
523
|
+
The health, prune, and PostgreSQL autovacuum commands accept `--backend` to target a non-default backend alias.
|
|
523
524
|
|
|
524
525
|
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
525
526
|
task import path, while `--task-key` filters recurring execution cleanup by
|
|
@@ -543,9 +544,11 @@ from dj_queue.api import (
|
|
|
543
544
|
discard_scheduled_jobs,
|
|
544
545
|
retry_failed_job,
|
|
545
546
|
retry_failed_jobs,
|
|
547
|
+
schedule_failed_job_retry,
|
|
546
548
|
)
|
|
547
549
|
|
|
548
550
|
retry_failed_job(job_id)
|
|
551
|
+
schedule_failed_job_retry(job_id, retry_at=retry_at)
|
|
549
552
|
discard_failed_job(job_id)
|
|
550
553
|
retry_failed_jobs(job_ids=[job_id_a, job_id_b], batch_size=2)
|
|
551
554
|
discard_ready_jobs(job_ids=[ready_job_id], batch_size=1)
|
|
@@ -554,7 +557,9 @@ discard_scheduled_jobs(job_ids=[scheduled_job_id], batch_size=1)
|
|
|
554
557
|
discard_blocked_jobs(job_ids=[blocked_job_id], batch_size=1)
|
|
555
558
|
```
|
|
556
559
|
|
|
557
|
-
Failures stay inspectable until you act on them.
|
|
560
|
+
Failures stay inspectable until you act on them. Scheduled failed retries stay
|
|
561
|
+
failed until a dispatcher promotes rows whose `retry_at` is due; `dj_queue` does
|
|
562
|
+
not add an automatic retry policy or backoff engine.
|
|
558
563
|
|
|
559
564
|
## Errors When Enqueuing
|
|
560
565
|
|
|
@@ -714,6 +719,9 @@ policy, and autovacuum tuning.
|
|
|
714
719
|
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
715
720
|
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
716
721
|
conservative for queue workloads.
|
|
722
|
+
- Use `python manage.py dj_queue_postgres_autovacuum` to print reviewable
|
|
723
|
+
`ALTER TABLE ... SET (...)` SQL for queue-table autovacuum storage
|
|
724
|
+
parameters. `dj_queue` does not apply these settings in migrations.
|
|
717
725
|
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
718
726
|
transactions pin dead tuples and delay vacuum.
|
|
719
727
|
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
@@ -839,7 +847,7 @@ Worker entry options:
|
|
|
839
847
|
| `threads` | `3` | worker threads per worker process |
|
|
840
848
|
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
841
849
|
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
842
|
-
| `prefetch_multiplier` | `2` |
|
|
850
|
+
| `prefetch_multiplier` | `2` | upper bound on running plus queued claimed work; workers only poll when at least one thread is idle |
|
|
843
851
|
|
|
844
852
|
Dispatcher entry options:
|
|
845
853
|
|
|
@@ -1020,6 +1028,10 @@ Exported metric families:
|
|
|
1020
1028
|
- `dj_queue_recurring_tasks{backend}`
|
|
1021
1029
|
- `dj_queue_semaphores{queue_database}`
|
|
1022
1030
|
- `dj_queue_process_rows{backend}`
|
|
1031
|
+
- `dj_queue_failed_jobs{backend}`
|
|
1032
|
+
- `dj_queue_failed_job_oldest_age_seconds{backend}`
|
|
1033
|
+
- `dj_queue_failed_job_retention_seconds{backend}`
|
|
1034
|
+
- `dj_queue_failed_jobs_over_retention{backend}`
|
|
1023
1035
|
|
|
1024
1036
|
Both endpoints support bearer token authentication. Set
|
|
1025
1037
|
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
@@ -491,9 +491,10 @@ python manage.py dj_queue_prune --failed-older-than 604800
|
|
|
491
491
|
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
492
492
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
493
493
|
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
494
|
+
python manage.py dj_queue_postgres_autovacuum
|
|
494
495
|
```
|
|
495
496
|
|
|
496
|
-
The health and
|
|
497
|
+
The health, prune, and PostgreSQL autovacuum commands accept `--backend` to target a non-default backend alias.
|
|
497
498
|
|
|
498
499
|
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
499
500
|
task import path, while `--task-key` filters recurring execution cleanup by
|
|
@@ -517,9 +518,11 @@ from dj_queue.api import (
|
|
|
517
518
|
discard_scheduled_jobs,
|
|
518
519
|
retry_failed_job,
|
|
519
520
|
retry_failed_jobs,
|
|
521
|
+
schedule_failed_job_retry,
|
|
520
522
|
)
|
|
521
523
|
|
|
522
524
|
retry_failed_job(job_id)
|
|
525
|
+
schedule_failed_job_retry(job_id, retry_at=retry_at)
|
|
523
526
|
discard_failed_job(job_id)
|
|
524
527
|
retry_failed_jobs(job_ids=[job_id_a, job_id_b], batch_size=2)
|
|
525
528
|
discard_ready_jobs(job_ids=[ready_job_id], batch_size=1)
|
|
@@ -528,7 +531,9 @@ discard_scheduled_jobs(job_ids=[scheduled_job_id], batch_size=1)
|
|
|
528
531
|
discard_blocked_jobs(job_ids=[blocked_job_id], batch_size=1)
|
|
529
532
|
```
|
|
530
533
|
|
|
531
|
-
Failures stay inspectable until you act on them.
|
|
534
|
+
Failures stay inspectable until you act on them. Scheduled failed retries stay
|
|
535
|
+
failed until a dispatcher promotes rows whose `retry_at` is due; `dj_queue` does
|
|
536
|
+
not add an automatic retry policy or backoff engine.
|
|
532
537
|
|
|
533
538
|
## Errors When Enqueuing
|
|
534
539
|
|
|
@@ -688,6 +693,9 @@ policy, and autovacuum tuning.
|
|
|
688
693
|
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
689
694
|
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
690
695
|
conservative for queue workloads.
|
|
696
|
+
- Use `python manage.py dj_queue_postgres_autovacuum` to print reviewable
|
|
697
|
+
`ALTER TABLE ... SET (...)` SQL for queue-table autovacuum storage
|
|
698
|
+
parameters. `dj_queue` does not apply these settings in migrations.
|
|
691
699
|
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
692
700
|
transactions pin dead tuples and delay vacuum.
|
|
693
701
|
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
@@ -813,7 +821,7 @@ Worker entry options:
|
|
|
813
821
|
| `threads` | `3` | worker threads per worker process |
|
|
814
822
|
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
815
823
|
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
816
|
-
| `prefetch_multiplier` | `2` |
|
|
824
|
+
| `prefetch_multiplier` | `2` | upper bound on running plus queued claimed work; workers only poll when at least one thread is idle |
|
|
817
825
|
|
|
818
826
|
Dispatcher entry options:
|
|
819
827
|
|
|
@@ -994,6 +1002,10 @@ Exported metric families:
|
|
|
994
1002
|
- `dj_queue_recurring_tasks{backend}`
|
|
995
1003
|
- `dj_queue_semaphores{queue_database}`
|
|
996
1004
|
- `dj_queue_process_rows{backend}`
|
|
1005
|
+
- `dj_queue_failed_jobs{backend}`
|
|
1006
|
+
- `dj_queue_failed_job_oldest_age_seconds{backend}`
|
|
1007
|
+
- `dj_queue_failed_job_retention_seconds{backend}`
|
|
1008
|
+
- `dj_queue_failed_jobs_over_retention{backend}`
|
|
997
1009
|
|
|
998
1010
|
Both endpoints support bearer token authentication. Set
|
|
999
1011
|
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
@@ -716,14 +716,14 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
716
716
|
class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
717
717
|
backend_filter_field = "job__backend_alias"
|
|
718
718
|
ignored_filter_params = ("job__backend_alias",)
|
|
719
|
-
list_display = ("job", "exception_class", "message", "display_created_at")
|
|
719
|
+
list_display = ("job", "exception_class", "message", "display_retry_at", "display_created_at")
|
|
720
720
|
list_filter = (
|
|
721
721
|
("job__queue_name", admin.AllValuesFieldListFilter),
|
|
722
722
|
"exception_class",
|
|
723
723
|
)
|
|
724
724
|
list_select_related = ("job",)
|
|
725
725
|
actions = ("retry_jobs", "discard_jobs")
|
|
726
|
-
readonly_fields = ("job", "exception_class", "message", "traceback", "created_at")
|
|
726
|
+
readonly_fields = ("job", "exception_class", "message", "traceback", "retry_at", "created_at")
|
|
727
727
|
search_fields = ("job__id", "job__task_path", "message", "exception_class")
|
|
728
728
|
|
|
729
729
|
@admin.action(description="Retry selected failed jobs")
|
|
@@ -756,6 +756,10 @@ class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
756
756
|
def display_created_at(self, obj):
|
|
757
757
|
return _format_admin_datetime(obj.created_at)
|
|
758
758
|
|
|
759
|
+
@admin.display(description="retry at", ordering="retry_at")
|
|
760
|
+
def display_retry_at(self, obj):
|
|
761
|
+
return _format_admin_datetime(obj.retry_at)
|
|
762
|
+
|
|
759
763
|
def get_change_actions(self, request, obj):
|
|
760
764
|
if obj is None:
|
|
761
765
|
return ()
|
|
@@ -15,6 +15,7 @@ from dj_queue.operations.jobs import (
|
|
|
15
15
|
execute_claimed_job,
|
|
16
16
|
retry_failed_job,
|
|
17
17
|
retry_failed_jobs,
|
|
18
|
+
schedule_failed_job_retry,
|
|
18
19
|
)
|
|
19
20
|
from dj_queue.operations.queues import pause_queue, resume_queue
|
|
20
21
|
from dj_queue.operations.recurring import schedule_recurring_task, unschedule_recurring_task
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"execute_claimed_job",
|
|
33
34
|
"retry_failed_job",
|
|
34
35
|
"retry_failed_jobs",
|
|
36
|
+
"schedule_failed_job_retry",
|
|
35
37
|
"schedule_recurring_task",
|
|
36
38
|
"unschedule_recurring_task",
|
|
37
39
|
]
|
|
@@ -157,21 +157,26 @@ def load_backend_config(
|
|
|
157
157
|
|
|
158
158
|
ensure_dj_queue_backend_alias(tasks_settings, backend_alias)
|
|
159
159
|
backend_block = _backend_block(tasks_settings, backend_alias)
|
|
160
|
-
|
|
160
|
+
env_key = tuple(env.get(key) for key in CONFIG_ENV_KEYS)
|
|
161
161
|
cache_key = (
|
|
162
162
|
backend_alias,
|
|
163
|
-
_cache_key(cli_overrides),
|
|
164
|
-
|
|
163
|
+
_EMPTY_CLI_CACHE_KEY if not cli_overrides else _cache_key(cli_overrides),
|
|
164
|
+
env_key,
|
|
165
165
|
_cache_key(backend_block),
|
|
166
166
|
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
cached = _BACKEND_CONFIG_CACHE.get(cache_key)
|
|
168
|
+
if cached is not None:
|
|
169
|
+
return cached
|
|
170
|
+
|
|
171
|
+
env_values = {key: value for key, value in zip(CONFIG_ENV_KEYS, env_key) if value is not None}
|
|
172
|
+
config = _load_backend_config_uncached(
|
|
173
|
+
backend_alias,
|
|
174
|
+
cli_overrides,
|
|
175
|
+
env_values,
|
|
176
|
+
tasks_settings,
|
|
177
|
+
)
|
|
178
|
+
_BACKEND_CONFIG_CACHE[cache_key] = config
|
|
179
|
+
return config
|
|
175
180
|
|
|
176
181
|
|
|
177
182
|
def load_allowed_queues(
|
|
@@ -776,3 +781,6 @@ def _cache_key(value: Any) -> str:
|
|
|
776
781
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
|
777
782
|
except (TypeError, ValueError) as exc:
|
|
778
783
|
raise ImproperlyConfigured("dj_queue config values must be JSON-serializable") from exc
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
_EMPTY_CLI_CACHE_KEY = _cache_key({})
|
|
@@ -32,17 +32,26 @@ def locked_queryset(qs, use_skip_locked: bool = True):
|
|
|
32
32
|
return qs.select_for_update(**select_for_update_kwargs)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
_CAPABILITIES_CACHE: dict[str, DatabaseCapabilities] = {}
|
|
36
|
+
|
|
37
|
+
|
|
35
38
|
def database_capabilities(alias: str) -> DatabaseCapabilities:
|
|
39
|
+
cached = _CAPABILITIES_CACHE.get(alias)
|
|
40
|
+
if cached is not None:
|
|
41
|
+
return cached
|
|
42
|
+
|
|
36
43
|
connection = connections[alias]
|
|
37
44
|
backend_family = _backend_family(connection)
|
|
38
45
|
supports_skip_locked_flag = bool(connection.features.has_select_for_update_skip_locked)
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
capabilities = DatabaseCapabilities(
|
|
41
48
|
backend_family=backend_family,
|
|
42
49
|
supports_skip_locked=supports_skip_locked_flag,
|
|
43
50
|
supports_listen_notify=backend_family == "postgresql",
|
|
44
51
|
uses_serialized_writes=backend_family == "sqlite",
|
|
45
52
|
)
|
|
53
|
+
_CAPABILITIES_CACHE[alias] = capabilities
|
|
54
|
+
return capabilities
|
|
46
55
|
|
|
47
56
|
|
|
48
57
|
def supports_skip_locked(alias: str) -> bool:
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from django.core.management.base import CommandError
|
|
3
|
+
from django.db import connections
|
|
4
|
+
|
|
5
|
+
from dj_queue import observability
|
|
6
|
+
from dj_queue.config import load_backend_config
|
|
7
|
+
from dj_queue.db import database_capabilities
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Command(BaseCommand):
|
|
11
|
+
help = "Print PostgreSQL autovacuum storage-parameter SQL for dj_queue tables"
|
|
12
|
+
|
|
13
|
+
def add_arguments(self, parser):
|
|
14
|
+
parser.add_argument("--backend", default="default")
|
|
15
|
+
|
|
16
|
+
def handle(self, *args, **options):
|
|
17
|
+
backend_alias = options["backend"]
|
|
18
|
+
database_alias = load_backend_config(backend_alias).database_alias
|
|
19
|
+
if database_capabilities(database_alias).backend_family != "postgresql":
|
|
20
|
+
raise CommandError("dj_queue_postgres_autovacuum requires PostgreSQL")
|
|
21
|
+
|
|
22
|
+
connection = connections[database_alias]
|
|
23
|
+
self.stdout.write("-- dj_queue PostgreSQL autovacuum guidance")
|
|
24
|
+
self.stdout.write("-- review before applying; dj_queue does not apply these in migrations")
|
|
25
|
+
for statement in observability.postgres_autovacuum_sql(connection):
|
|
26
|
+
self.stdout.write(statement)
|
|
@@ -32,6 +32,10 @@ def metric_families(*, snapshots=None):
|
|
|
32
32
|
recurring_tasks = []
|
|
33
33
|
semaphores = []
|
|
34
34
|
process_rows = []
|
|
35
|
+
failed_jobs = []
|
|
36
|
+
failed_oldest_age = []
|
|
37
|
+
failed_retention = []
|
|
38
|
+
failed_over_retention = []
|
|
35
39
|
seen_queue_databases = set()
|
|
36
40
|
|
|
37
41
|
for snapshot in snapshots:
|
|
@@ -95,6 +99,33 @@ def metric_families(*, snapshots=None):
|
|
|
95
99
|
value=len(snapshot.process_rows),
|
|
96
100
|
)
|
|
97
101
|
)
|
|
102
|
+
if snapshot.failed_metrics is not None:
|
|
103
|
+
failed_jobs.append(
|
|
104
|
+
MetricSample(
|
|
105
|
+
labels=(backend_alias,),
|
|
106
|
+
value=snapshot.failed_metrics["count"],
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
if snapshot.failed_metrics["oldest_age_seconds"] is not None:
|
|
110
|
+
failed_oldest_age.append(
|
|
111
|
+
MetricSample(
|
|
112
|
+
labels=(backend_alias,),
|
|
113
|
+
value=snapshot.failed_metrics["oldest_age_seconds"],
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
if snapshot.failed_metrics["retention_seconds"] is not None:
|
|
117
|
+
failed_retention.append(
|
|
118
|
+
MetricSample(
|
|
119
|
+
labels=(backend_alias,),
|
|
120
|
+
value=snapshot.failed_metrics["retention_seconds"],
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
failed_over_retention.append(
|
|
124
|
+
MetricSample(
|
|
125
|
+
labels=(backend_alias,),
|
|
126
|
+
value=snapshot.failed_metrics["over_retention_count"],
|
|
127
|
+
)
|
|
128
|
+
)
|
|
98
129
|
|
|
99
130
|
if queue_database_alias in seen_queue_databases:
|
|
100
131
|
continue
|
|
@@ -170,4 +201,32 @@ def metric_families(*, snapshots=None):
|
|
|
170
201
|
labels=("backend",),
|
|
171
202
|
samples=tuple(process_rows),
|
|
172
203
|
),
|
|
204
|
+
MetricFamily(
|
|
205
|
+
name="dj_queue_failed_jobs",
|
|
206
|
+
help_text="Current failed job count by backend",
|
|
207
|
+
metric_type="gauge",
|
|
208
|
+
labels=("backend",),
|
|
209
|
+
samples=tuple(failed_jobs),
|
|
210
|
+
),
|
|
211
|
+
MetricFamily(
|
|
212
|
+
name="dj_queue_failed_job_oldest_age_seconds",
|
|
213
|
+
help_text="Age of the oldest failed job by backend",
|
|
214
|
+
metric_type="gauge",
|
|
215
|
+
labels=("backend",),
|
|
216
|
+
samples=tuple(failed_oldest_age),
|
|
217
|
+
),
|
|
218
|
+
MetricFamily(
|
|
219
|
+
name="dj_queue_failed_job_retention_seconds",
|
|
220
|
+
help_text="Configured failed-job retention window by backend",
|
|
221
|
+
metric_type="gauge",
|
|
222
|
+
labels=("backend",),
|
|
223
|
+
samples=tuple(failed_retention),
|
|
224
|
+
),
|
|
225
|
+
MetricFamily(
|
|
226
|
+
name="dj_queue_failed_jobs_over_retention",
|
|
227
|
+
help_text="Current failed job count older than configured retention by backend",
|
|
228
|
+
metric_type="gauge",
|
|
229
|
+
labels=("backend",),
|
|
230
|
+
samples=tuple(failed_over_retention),
|
|
231
|
+
),
|
|
173
232
|
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Generated by Django 6.0.5 on 2026-06-06 09:11
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("dj_queue", "0012_job_djq_jobs_b_queue_id_idx_job_djq_jobs_b_conc_idx_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="failedexecution",
|
|
14
|
+
name="retry_at",
|
|
15
|
+
field=models.DateTimeField(blank=True, null=True),
|
|
16
|
+
),
|
|
17
|
+
migrations.AddIndex(
|
|
18
|
+
model_name="failedexecution",
|
|
19
|
+
index=models.Index(fields=["retry_at", "job"], name="dj_queue_fa_retry_a_2ff769_idx"),
|
|
20
|
+
),
|
|
21
|
+
]
|
|
@@ -281,8 +281,12 @@ class FailedExecution(models.Model):
|
|
|
281
281
|
exception_class = models.CharField(max_length=255)
|
|
282
282
|
message = models.TextField(default="")
|
|
283
283
|
traceback = models.TextField(default="")
|
|
284
|
+
retry_at = models.DateTimeField(null=True, blank=True)
|
|
284
285
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
285
286
|
|
|
286
287
|
class Meta:
|
|
287
288
|
db_table = "dj_queue_failed_executions"
|
|
288
|
-
indexes = [
|
|
289
|
+
indexes = [
|
|
290
|
+
models.Index(fields=["created_at", "job"]),
|
|
291
|
+
models.Index(fields=["retry_at", "job"]),
|
|
292
|
+
]
|