localqueue 0.1.1__tar.gz → 0.3.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.
- {localqueue-0.1.1 → localqueue-0.3.0}/CHANGELOG.md +24 -0
- localqueue-0.3.0/PKG-INFO +83 -0
- localqueue-0.3.0/README.md +48 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/docs/api.md +36 -1
- localqueue-0.3.0/docs/compare.md +50 -0
- localqueue-0.3.0/docs/index.md +74 -0
- localqueue-0.3.0/docs/operational-maturity.md +137 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/docs/queues.md +253 -6
- {localqueue-0.1.1 → localqueue-0.3.0}/docs/release.md +1 -1
- {localqueue-0.1.1 → localqueue-0.3.0}/docs/retries.md +95 -0
- localqueue-0.3.0/docs/stability.md +59 -0
- localqueue-0.3.0/examples/process_webhook.sh +10 -0
- localqueue-0.3.0/examples/sqlite_concurrency_benchmark.py +181 -0
- localqueue-0.3.0/examples/sqlite_process_harness.py +438 -0
- localqueue-0.3.0/localqueue/cli.py +1355 -0
- localqueue-0.3.0/localqueue/failure.py +27 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/queue.py +48 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/store.py +80 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/store.py +717 -18
- localqueue-0.3.0/localqueue/worker.py +388 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/pyproject.toml +1 -1
- localqueue-0.3.0/tests/test_cli.py +1977 -0
- localqueue-0.3.0/tests/test_process_harness.py +77 -0
- localqueue-0.3.0/tests/test_queue.py +1760 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/tests/test_retry.py +65 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/uv.lock +1 -1
- localqueue-0.1.1/PKG-INFO +0 -364
- localqueue-0.1.1/README.md +0 -329
- localqueue-0.1.1/docs/index.md +0 -208
- localqueue-0.1.1/docs/operational-maturity.md +0 -96
- localqueue-0.1.1/localqueue/cli.py +0 -744
- localqueue-0.1.1/localqueue/worker.py +0 -192
- localqueue-0.1.1/tests/test_cli.py +0 -988
- localqueue-0.1.1/tests/test_queue.py +0 -907
- {localqueue-0.1.1 → localqueue-0.3.0}/.gitignore +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/LICENSE +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/examples/email_worker.py +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/examples/enqueue_email.py +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/__init__.py +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/__init__.py +0 -0
- {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/tenacity.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- Add a concurrent SQLite stress test for multiple producers and consumers.
|
|
8
|
+
- Add `examples/sqlite_concurrency_benchmark.py` for measuring local queue throughput.
|
|
9
|
+
- Make the SQLite benchmark reset its store and retry transient lock contention.
|
|
10
|
+
- Add `examples/sqlite_process_harness.py` for process-level throughput and crash-recovery checks.
|
|
11
|
+
- Add process-based stress tests for SQLite producer and consumer coordination.
|
|
12
|
+
- Add dead-letter filters and summaries for `queue dead`.
|
|
13
|
+
- Add enqueue deduplication with `--dedupe-key` and `dedupe_key=`.
|
|
14
|
+
- Add queue-level retry defaults that workers can inherit from `PersistentQueue`.
|
|
15
|
+
- Add worker rate limiting and circuit-breaker controls.
|
|
16
|
+
- Add usage docs for rate limiting, circuit breaker, and queue positioning.
|
|
17
|
+
- Add a short-term maturity note focused on performance and guarantees.
|
|
18
|
+
|
|
19
|
+
## 0.2.0
|
|
20
|
+
|
|
21
|
+
- Add `queue stats --watch` for monitoring queue counts while workers run.
|
|
22
|
+
- Add `queue dead --watch` for repeatedly listing dead letters.
|
|
23
|
+
- Add `queue requeue-dead --all` for bulk recovery after a fix.
|
|
24
|
+
- Add structured `command not found` handling for `queue exec`.
|
|
25
|
+
- Add `examples/process_webhook.sh` as a shell/curl worker example.
|
|
26
|
+
|
|
3
27
|
## 0.1.1
|
|
4
28
|
|
|
5
29
|
- Add project URLs for PyPI metadata.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: localqueue
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Durable local queues for Python, with persistent retry state powered by Tenacity.
|
|
5
|
+
Project-URL: Homepage, https://brunoportis.github.io/localqueue/
|
|
6
|
+
Project-URL: Documentation, https://brunoportis.github.io/localqueue/
|
|
7
|
+
Project-URL: Repository, https://github.com/brunoportis/localqueue
|
|
8
|
+
Project-URL: Issues, https://github.com/brunoportis/localqueue/issues
|
|
9
|
+
Author: Bruno Portis
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: tenacity>=9.1.4
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: lmdb>=2.2.0; extra == 'all'
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'all'
|
|
25
|
+
Requires-Dist: rich>=15.0.0; extra == 'all'
|
|
26
|
+
Requires-Dist: typer>=0.16.0; extra == 'all'
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Requires-Dist: pyyaml>=6.0.3; extra == 'cli'
|
|
29
|
+
Requires-Dist: rich>=15.0.0; extra == 'cli'
|
|
30
|
+
Requires-Dist: typer>=0.16.0; extra == 'cli'
|
|
31
|
+
Provides-Extra: lmdb
|
|
32
|
+
Requires-Dist: lmdb>=2.2.0; extra == 'lmdb'
|
|
33
|
+
Provides-Extra: sqlite
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# `localqueue`
|
|
37
|
+
|
|
38
|
+
[](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml)
|
|
39
|
+
[](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql)
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
`localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
|
|
43
|
+
|
|
44
|
+
Use it for scripts, CLI tools, cron jobs, and small Python workers that share one local store. Use `localqueue.retry` when another system already delivers work and you only need retry state that survives restarts.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
localqueue queue exec emails -- python scripts/send_email.py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from localqueue import PersistentQueue, persistent_worker
|
|
52
|
+
|
|
53
|
+
queue = PersistentQueue("emails")
|
|
54
|
+
queue.put({"to": "user@example.com"})
|
|
55
|
+
|
|
56
|
+
@persistent_worker(queue)
|
|
57
|
+
def send_email(job: dict[str, str]) -> None:
|
|
58
|
+
deliver(job["to"])
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install localqueue
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For the CLI:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install "localqueue[cli]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For the optional LMDB backend:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "localqueue[lmdb]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## When not to use
|
|
82
|
+
|
|
83
|
+
`localqueue` is not a distributed broker. If you need multi-host coordination, high write concurrency, managed retention, or strict cross-service ordering, use a system designed for that operating model.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# `localqueue`
|
|
2
|
+
|
|
3
|
+
[](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml)
|
|
4
|
+
[](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql)
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
`localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
|
|
8
|
+
|
|
9
|
+
Use it for scripts, CLI tools, cron jobs, and small Python workers that share one local store. Use `localqueue.retry` when another system already delivers work and you only need retry state that survives restarts.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
localqueue queue exec emails -- python scripts/send_email.py
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from localqueue import PersistentQueue, persistent_worker
|
|
17
|
+
|
|
18
|
+
queue = PersistentQueue("emails")
|
|
19
|
+
queue.put({"to": "user@example.com"})
|
|
20
|
+
|
|
21
|
+
@persistent_worker(queue)
|
|
22
|
+
def send_email(job: dict[str, str]) -> None:
|
|
23
|
+
deliver(job["to"])
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install localqueue
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For the CLI:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install "localqueue[cli]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For the optional LMDB backend:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install "localqueue[lmdb]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## When not to use
|
|
47
|
+
|
|
48
|
+
`localqueue` is not a distributed broker. If you need multi-host coordination, high write concurrency, managed retention, or strict cross-service ordering, use a system designed for that operating model.
|
|
@@ -24,6 +24,7 @@ Constructor options:
|
|
|
24
24
|
| `store_path` | path for the default SQLite queue store |
|
|
25
25
|
| `lease_timeout` | seconds before an inflight message is redelivered |
|
|
26
26
|
| `maxsize` | maximum number of ready messages; `0` means unbounded |
|
|
27
|
+
| `retry_defaults` | Tenacity retry keyword defaults inherited by workers |
|
|
27
28
|
|
|
28
29
|
Core methods:
|
|
29
30
|
|
|
@@ -44,6 +45,8 @@ Core methods:
|
|
|
44
45
|
| `stats()` | count ready, delayed, inflight, dead, and total messages |
|
|
45
46
|
| `dead_letters(limit=None)` | list dead-letter messages |
|
|
46
47
|
| `requeue_dead(message, delay=0.0)` | return a dead-letter message to ready delivery |
|
|
48
|
+
| `prune_dead_letters(older_than)` | remove dead letters older than the given age |
|
|
49
|
+
| `count_dead_letters_older_than(older_than)` | count dead letters older than the given age without removing them |
|
|
47
50
|
| `empty()` | whether there are no ready messages |
|
|
48
51
|
| `full()` | whether ready capacity is reached |
|
|
49
52
|
| `purge()` | remove all queue records |
|
|
@@ -74,8 +77,21 @@ Constructor options:
|
|
|
74
77
|
| `dead_letter_on_failure` | dead-letter final handler failures when `True` |
|
|
75
78
|
| `dead_letter_on_exhaustion` | compatibility alias for `dead_letter_on_failure` |
|
|
76
79
|
| `release_delay` | delay used when releasing failed messages |
|
|
80
|
+
| `min_interval` | minimum seconds to wait between worker message starts |
|
|
81
|
+
| `circuit_breaker_failures` | consecutive recoverable failures before pausing the worker |
|
|
82
|
+
| `circuit_breaker_cooldown` | pause duration after the breaker opens |
|
|
77
83
|
| `**retry_kwargs` | forwarded to `PersistentRetrying` |
|
|
78
84
|
|
|
85
|
+
Queue-level retry defaults can also be attached to `PersistentQueue` and are
|
|
86
|
+
merged into worker retry kwargs before explicit worker overrides. That keeps
|
|
87
|
+
shared queue policies close to the queue definition while still letting a
|
|
88
|
+
worker override a specific retry parameter when needed.
|
|
89
|
+
|
|
90
|
+
`min_interval` is a per-worker rate limit. Set it when a handler talks to an
|
|
91
|
+
external service that should not be hit back-to-back. The circuit-breaker pair
|
|
92
|
+
(`circuit_breaker_failures` + `circuit_breaker_cooldown`) opens after repeated
|
|
93
|
+
recoverable failures and pauses the worker before it fetches the next message.
|
|
94
|
+
|
|
79
95
|
#### `QueueMessage`
|
|
80
96
|
|
|
81
97
|
Dataclass returned by `put()` and `get_message()`.
|
|
@@ -91,12 +107,17 @@ Dataclass returned by `put()` and `get_message()`.
|
|
|
91
107
|
| `available_at` | earliest delivery timestamp |
|
|
92
108
|
| `leased_until` | lease expiration timestamp, if inflight |
|
|
93
109
|
| `leased_by` | optional worker id that currently owns the lease, if inflight |
|
|
110
|
+
| `dedupe_key` | optional idempotency key used to reuse the same stored message |
|
|
111
|
+
| `attempt_history` | list of lease and outcome events recorded for this message |
|
|
94
112
|
| `last_error` | structured error from the most recent failed processing attempt, if recorded |
|
|
95
113
|
| `failed_at` | timestamp for `last_error`, if recorded |
|
|
96
114
|
|
|
97
115
|
For `localqueue queue exec` failures, `last_error` also includes `command`,
|
|
98
116
|
`exit_code`, `stdout`, and `stderr` fields so command workers can be inspected
|
|
99
|
-
from `queue inspect` and `queue dead`.
|
|
117
|
+
from `queue inspect` and `queue dead`. `dedupe_key` is returned on inspection
|
|
118
|
+
and lets repeated enqueues reuse the same message until it is acknowledged or
|
|
119
|
+
cleaned up. `attempt_history` shows the lease and terminal events that led to
|
|
120
|
+
the current state.
|
|
100
121
|
|
|
101
122
|
#### `QueueStats`
|
|
102
123
|
|
|
@@ -109,6 +130,12 @@ Dataclass returned by `stats()`.
|
|
|
109
130
|
| `inflight` | leased messages not yet acknowledged, released, or dead-lettered |
|
|
110
131
|
| `dead` | dead-letter messages hidden from normal delivery |
|
|
111
132
|
| `total` | all messages still stored for the queue |
|
|
133
|
+
| `by_worker_id` | current inflight counts grouped by `leased_by` |
|
|
134
|
+
| `leases_by_worker_id` | historical lease counts grouped by `leased_by`, a coarse throughput proxy |
|
|
135
|
+
| `last_seen_by_worker_id` | most recent heartbeat timestamp for each recorded worker id |
|
|
136
|
+
| `oldest_ready_age_seconds` | age of the oldest ready message currently waiting |
|
|
137
|
+
| `oldest_inflight_age_seconds` | age of the oldest current inflight lease |
|
|
138
|
+
| `average_inflight_age_seconds` | average age across current inflight leases |
|
|
112
139
|
|
|
113
140
|
### Worker decorators
|
|
114
141
|
|
|
@@ -155,6 +182,10 @@ Protocol for custom queue stores.
|
|
|
155
182
|
SQLite-backed queue store. This is the default backend. Records are serialized
|
|
156
183
|
as versioned JSON; values must be JSON-serializable.
|
|
157
184
|
|
|
185
|
+
The SQLite store tracks its on-disk schema version with `PRAGMA user_version`.
|
|
186
|
+
Current releases accept older compatible versions and reject future versions
|
|
187
|
+
until a migration is defined.
|
|
188
|
+
|
|
158
189
|
#### `LMDBQueueStore`
|
|
159
190
|
|
|
160
191
|
LMDB-backed queue store. Records are serialized as versioned JSON; values must be
|
|
@@ -168,6 +199,9 @@ Thread-safe in-memory queue store for tests.
|
|
|
168
199
|
|
|
169
200
|
Raised when LMDB reports that the queue store is locked by another process.
|
|
170
201
|
|
|
202
|
+
Install `localqueue[cli]` when you want the CLI entry points, and
|
|
203
|
+
`localqueue[lmdb]` when you want the LMDB queue store backend.
|
|
204
|
+
|
|
171
205
|
## localqueue.retry
|
|
172
206
|
|
|
173
207
|
### Retry decorators
|
|
@@ -316,6 +350,7 @@ class AttemptStore:
|
|
|
316
350
|
def load(self, key: str) -> RetryRecord | None: ...
|
|
317
351
|
def save(self, key: str, record: RetryRecord) -> None: ...
|
|
318
352
|
def delete(self, key: str) -> None: ...
|
|
353
|
+
def prune_exhausted(self, *, older_than: float, now: float) -> int: ...
|
|
319
354
|
```
|
|
320
355
|
|
|
321
356
|
#### `LMDBAttemptStore`
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/inbox
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Compare
|
|
6
|
+
|
|
7
|
+
`localqueue` is for local, durable queues on one machine. If your jobs need a broker, a cluster, or managed retention, pick a tool that is built for that shape.
|
|
8
|
+
|
|
9
|
+
## Use `localqueue` when
|
|
10
|
+
|
|
11
|
+
- you want a local filesystem-backed queue
|
|
12
|
+
- jobs are handled by scripts, CLIs, or small Python workers
|
|
13
|
+
- you care more about operational simplicity than distributed scheduling
|
|
14
|
+
- at-least-once delivery is acceptable
|
|
15
|
+
- you want to inspect and requeue failed work from the terminal
|
|
16
|
+
- you want worker-side controls like `min_interval`, queue-level retry defaults,
|
|
17
|
+
and a simple circuit breaker without introducing a broker
|
|
18
|
+
|
|
19
|
+
## Reach for something else when
|
|
20
|
+
|
|
21
|
+
- producers and consumers must run on different machines
|
|
22
|
+
- you need broker-managed delivery guarantees
|
|
23
|
+
- write contention is high
|
|
24
|
+
- retention, metrics, sharding, or fanout are first-class requirements
|
|
25
|
+
- you want a queue that is already part of a larger distributed platform
|
|
26
|
+
- you need broker-level rate limiting or cross-host worker coordination
|
|
27
|
+
|
|
28
|
+
## Quick comparison
|
|
29
|
+
|
|
30
|
+
| System | Best fit | Why choose it instead |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `localqueue` | single-host workers, scripts, local ops | simplest path when the queue can live on one filesystem |
|
|
33
|
+
| Celery | Python task orchestration with a broker | richer distributed worker ecosystem |
|
|
34
|
+
| RQ | Redis-backed Python jobs | simpler Redis-based queueing for server-side apps |
|
|
35
|
+
| Dramatiq | Python workers with broker support | clean task API for distributed workers |
|
|
36
|
+
| SQS | managed cloud queueing | external durability and multi-host coordination |
|
|
37
|
+
| RabbitMQ | brokered messaging | mature routing and delivery primitives |
|
|
38
|
+
| Redis Streams | stream processing and consumer groups | shared stream coordination with Redis |
|
|
39
|
+
| Kafka | high-throughput event pipelines | log-based distributed ingestion and replay |
|
|
40
|
+
|
|
41
|
+
## Rule of thumb
|
|
42
|
+
|
|
43
|
+
If the queue can stay local and the value is in simple recovery, `localqueue` fits.
|
|
44
|
+
If the queue itself is part of a distributed system boundary, use a broker or stream platform.
|
|
45
|
+
|
|
46
|
+
See also:
|
|
47
|
+
|
|
48
|
+
- [Operational maturity](operational-maturity.md)
|
|
49
|
+
- [Persistent queues](queues.md)
|
|
50
|
+
- [Persistent retries](retries.md)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/inbox
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Overview
|
|
6
|
+
|
|
7
|
+
`localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
|
|
8
|
+
|
|
9
|
+
It fits scripts, CLI tools, cron jobs, development helpers, and small workers that can safely share one local store. It is not a distributed broker and does not provide multi-host coordination.
|
|
10
|
+
|
|
11
|
+
!!! note
|
|
12
|
+
The default model is local-file storage, at-least-once delivery, and best-effort ordering under concurrency.
|
|
13
|
+
|
|
14
|
+
## CLI
|
|
15
|
+
|
|
16
|
+
Use the CLI when you want to enqueue, inspect, watch, and recover jobs from the terminal.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
localqueue queue add emails --value '{"to":"user@example.com"}'
|
|
20
|
+
localqueue queue exec emails -- python scripts/send_email.py
|
|
21
|
+
localqueue queue stats emails --watch --interval 1
|
|
22
|
+
localqueue queue dead emails --summary
|
|
23
|
+
localqueue queue health emails
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`queue exec` runs an external command per message. The message value is written to stdin as JSON, and exit code `0` acks the message.
|
|
27
|
+
|
|
28
|
+
For more command examples, see [Persistent queues](queues.md).
|
|
29
|
+
|
|
30
|
+
## Library
|
|
31
|
+
|
|
32
|
+
Use the Python API when the worker lives in your codebase.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from localqueue import PersistentQueue, persistent_worker
|
|
36
|
+
|
|
37
|
+
queue = PersistentQueue("emails")
|
|
38
|
+
queue.put({"to": "user@example.com"})
|
|
39
|
+
|
|
40
|
+
@persistent_worker(queue)
|
|
41
|
+
def send_email(job: dict[str, str]) -> None:
|
|
42
|
+
deliver(job["to"])
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`localqueue.retry` stays available when another system already delivers work and you only need retry state.
|
|
46
|
+
|
|
47
|
+
See [Persistent retries](retries.md) for the retry API and store options.
|
|
48
|
+
|
|
49
|
+
## When to use
|
|
50
|
+
|
|
51
|
+
- small Python workers on one machine
|
|
52
|
+
- scripts that need durable work after restarts
|
|
53
|
+
- terminal-driven queues for jobs you want to inspect and requeue
|
|
54
|
+
- retry state that must survive process restarts
|
|
55
|
+
|
|
56
|
+
## When not to use
|
|
57
|
+
|
|
58
|
+
- multi-host scheduling
|
|
59
|
+
- high write concurrency
|
|
60
|
+
- managed retention or broker-level metrics
|
|
61
|
+
- strict global ordering
|
|
62
|
+
- distributed coordination
|
|
63
|
+
|
|
64
|
+
See [Compare](compare.md) for a short decision guide.
|
|
65
|
+
|
|
66
|
+
## Read more
|
|
67
|
+
|
|
68
|
+
- [Persistent queues](queues.md)
|
|
69
|
+
- [Persistent retries](retries.md)
|
|
70
|
+
- [Stability](stability.md)
|
|
71
|
+
- [Compare](compare.md)
|
|
72
|
+
- [API reference](api.md)
|
|
73
|
+
- [Operational maturity](operational-maturity.md)
|
|
74
|
+
- [Release checklist](release.md)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/list-checks
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Operational Maturity Checklist
|
|
6
|
+
|
|
7
|
+
This project is intentionally small: durable local queues for Python workers,
|
|
8
|
+
plus persistent retry state powered by Tenacity. The checklist below tracks the
|
|
9
|
+
work needed before describing it as a mature production queue system instead of
|
|
10
|
+
a local worker library.
|
|
11
|
+
|
|
12
|
+
## Near-term focus
|
|
13
|
+
|
|
14
|
+
The next release should improve two things that matter for real local workers:
|
|
15
|
+
|
|
16
|
+
- performance, especially under multiple producers and consumers sharing one
|
|
17
|
+
SQLite file
|
|
18
|
+
- guarantees, especially where the project is intentionally at-least-once and
|
|
19
|
+
best-effort rather than distributed or strongly ordered
|
|
20
|
+
|
|
21
|
+
That means measuring the current ceiling before promising more:
|
|
22
|
+
|
|
23
|
+
- benchmark WAL contention and queue throughput with concurrent processes
|
|
24
|
+
- use `examples/sqlite_concurrency_benchmark.py` to get a repeatable local
|
|
25
|
+
throughput snapshot
|
|
26
|
+
- a quick local run on the development machine stayed in the low thousands of
|
|
27
|
+
messages per second and handled a few producers and consumers without failing
|
|
28
|
+
the queue, which is useful as a local ceiling check but not a production
|
|
29
|
+
guarantee
|
|
30
|
+
- exercise abrupt consumer exit and lease recovery with
|
|
31
|
+
`examples/sqlite_process_harness.py --mode crash-recovery`
|
|
32
|
+
- document the practical limits for a single host and a single SQLite file
|
|
33
|
+
- keep ordering language explicit: best effort under concurrency, not strict
|
|
34
|
+
global ordering
|
|
35
|
+
- keep delivery language explicit: at least once, not exactly once
|
|
36
|
+
- keep recovery language explicit: inspection and requeue are local operations,
|
|
37
|
+
not cross-cluster coordination
|
|
38
|
+
|
|
39
|
+
## Concurrency and locking
|
|
40
|
+
|
|
41
|
+
- [x] Document practical SQLite concurrency limits for producers and workers.
|
|
42
|
+
- [x] Add stress tests for multiple producer and consumer processes sharing one
|
|
43
|
+
SQLite queue file.
|
|
44
|
+
- [x] Measure throughput and lock contention under WAL mode.
|
|
45
|
+
- [ ] Define guidance for when users should move to Postgres, Redis, SQS,
|
|
46
|
+
RabbitMQ, or another external broker.
|
|
47
|
+
|
|
48
|
+
## Distributed operation
|
|
49
|
+
|
|
50
|
+
- [x] Decide whether multi-host operation is in scope.
|
|
51
|
+
- [x] Add explicit documentation that the default model is local-file storage,
|
|
52
|
+
not distributed coordination.
|
|
53
|
+
- [x] Evaluate worker heartbeats beyond lease expiration.
|
|
54
|
+
- [x] Evaluate worker identity, liveness inspection, and stale-worker reporting.
|
|
55
|
+
- [x] Evaluate sharding or namespacing guidance for independent queues.
|
|
56
|
+
|
|
57
|
+
`localqueue` does not try to coordinate work across multiple machines. The
|
|
58
|
+
supported model is a single host, one local store, and local recovery through
|
|
59
|
+
inspection, retry, and requeue. Heartbeats and stale-worker reporting are
|
|
60
|
+
inspection tools for that local model, not a distributed liveness protocol.
|
|
61
|
+
|
|
62
|
+
## Delivery semantics
|
|
63
|
+
|
|
64
|
+
- [x] Document at-least-once delivery prominently in the README and queue docs.
|
|
65
|
+
- [x] Add idempotency guidance for handlers.
|
|
66
|
+
- [x] Provide examples for idempotent job keys and external side effects.
|
|
67
|
+
- [x] Evaluate deduplication support for enqueue operations.
|
|
68
|
+
- [x] Evaluate stronger ordering guarantees, or document why ordering is best
|
|
69
|
+
effort under concurrency.
|
|
70
|
+
- [x] Document the failure window between a successful handler side effect and
|
|
71
|
+
`ack()`.
|
|
72
|
+
|
|
73
|
+
## Retry and worker policies
|
|
74
|
+
|
|
75
|
+
- [x] Add examples for different policies by exception type.
|
|
76
|
+
- [x] Evaluate built-in permanent-failure classification.
|
|
77
|
+
- [x] Evaluate queue-level retry defaults that can be reused by many workers.
|
|
78
|
+
- [x] Evaluate rate limiting for handlers that call external services.
|
|
79
|
+
- [x] Evaluate circuit-breaker integration or documentation.
|
|
80
|
+
- [x] Add clearer guidance for `release_delay`, Tenacity `wait`, and
|
|
81
|
+
`lease_timeout` interactions.
|
|
82
|
+
- [x] Reject ambiguous worker-loop combinations such as `--forever` with
|
|
83
|
+
`--max-jobs`.
|
|
84
|
+
|
|
85
|
+
## Observability
|
|
86
|
+
|
|
87
|
+
- [ ] Add optional Prometheus or OpenTelemetry metrics.
|
|
88
|
+
- [x] Track per-message attempt history, not only the latest error.
|
|
89
|
+
- [x] Track queue latency, handler duration, and time spent inflight.
|
|
90
|
+
- [x] Track throughput by queue and worker id.
|
|
91
|
+
- [x] Add aggregate failure summaries for dead-letter inspection.
|
|
92
|
+
- [x] Evaluate structured event logging for enqueue, lease, ack, release, and
|
|
93
|
+
dead-letter transitions.
|
|
94
|
+
|
|
95
|
+
## Storage operations
|
|
96
|
+
|
|
97
|
+
- [x] Define a schema migration strategy for SQLite stores.
|
|
98
|
+
- [x] Document backup and restore expectations.
|
|
99
|
+
- [x] Add maintenance guidance for `VACUUM`, WAL files, and long-lived stores.
|
|
100
|
+
- [x] Add retention or cleanup controls for dead-letter records.
|
|
101
|
+
- [x] Add cleanup controls for old retry records.
|
|
102
|
+
- [x] Add preview mode before cleanup removes records.
|
|
103
|
+
- [x] Add configurable retention defaults for dead letters and retry records.
|
|
104
|
+
- [x] Add a one-shot queue health summary for day-to-day checks.
|
|
105
|
+
- [x] Document recovery behavior for corrupted or partially written store files.
|
|
106
|
+
- [x] Add compatibility tests for SQLite store files created by older package
|
|
107
|
+
versions.
|
|
108
|
+
|
|
109
|
+
The SQLite store keeps a schema version in `PRAGMA user_version`. Current
|
|
110
|
+
releases accept older compatible versions and reject future versions they do
|
|
111
|
+
not know how to migrate yet. That keeps upgrades explicit and leaves a clear
|
|
112
|
+
path for future schema changes.
|
|
113
|
+
|
|
114
|
+
## API stability
|
|
115
|
+
|
|
116
|
+
- [x] Decide which APIs are stable before a `1.0` release.
|
|
117
|
+
- [x] Document compatibility aliases and their planned lifetime.
|
|
118
|
+
- [x] Add deprecation policy.
|
|
119
|
+
- [ ] Increase coverage margin above the current 95% gate.
|
|
120
|
+
- [x] Keep docs aligned with defaults for SQLite, optional LMDB, and CLI extras.
|
|
121
|
+
|
|
122
|
+
## Guardrails against misuse
|
|
123
|
+
|
|
124
|
+
- [x] Document recommended payload size limits.
|
|
125
|
+
- [x] Add clearer lease-timeout guidance for slow jobs.
|
|
126
|
+
- [x] Add examples for safe shutdown and long-running workers.
|
|
127
|
+
- [x] Add health-check examples for worker processes.
|
|
128
|
+
- [x] Add configuration validation for risky combinations where possible.
|
|
129
|
+
- [x] Add warnings or docs for unbounded queue growth.
|
|
130
|
+
|
|
131
|
+
## Positioning
|
|
132
|
+
|
|
133
|
+
- [x] Keep public wording focused on durable local queues for scripts and small
|
|
134
|
+
workers.
|
|
135
|
+
- [x] Avoid presenting the project as a replacement for distributed brokers.
|
|
136
|
+
- [x] Add production-readiness notes that distinguish supported use cases from
|
|
137
|
+
future work.
|