solid-jobs-py 0.1.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.
- solid_jobs_py-0.1.0/PKG-INFO +231 -0
- solid_jobs_py-0.1.0/README.md +224 -0
- solid_jobs_py-0.1.0/pyproject.toml +20 -0
- solid_jobs_py-0.1.0/setup.cfg +4 -0
- solid_jobs_py-0.1.0/src/solid_jobs/__init__.py +28 -0
- solid_jobs_py-0.1.0/src/solid_jobs/__main__.py +5 -0
- solid_jobs_py-0.1.0/src/solid_jobs/blueprint.py +164 -0
- solid_jobs_py-0.1.0/src/solid_jobs/catalog.py +285 -0
- solid_jobs_py-0.1.0/src/solid_jobs/claim.py +319 -0
- solid_jobs_py-0.1.0/src/solid_jobs/conductor.py +235 -0
- solid_jobs_py-0.1.0/src/solid_jobs/console.py +118 -0
- solid_jobs_py-0.1.0/src/solid_jobs/engine.py +179 -0
- solid_jobs_py-0.1.0/src/solid_jobs/errors.py +38 -0
- solid_jobs_py-0.1.0/src/solid_jobs/executor.py +36 -0
- solid_jobs_py-0.1.0/src/solid_jobs/failure_policy.py +98 -0
- solid_jobs_py-0.1.0/src/solid_jobs/gateway.py +111 -0
- solid_jobs_py-0.1.0/src/solid_jobs/heartbeat.py +53 -0
- solid_jobs_py-0.1.0/src/solid_jobs/instrumentation.py +48 -0
- solid_jobs_py-0.1.0/src/solid_jobs/integrity_check.py +164 -0
- solid_jobs_py-0.1.0/src/solid_jobs/interceptors.py +48 -0
- solid_jobs_py-0.1.0/src/solid_jobs/ipc.py +136 -0
- solid_jobs_py-0.1.0/src/solid_jobs/keyspace.py +40 -0
- solid_jobs_py-0.1.0/src/solid_jobs/lab.py +53 -0
- solid_jobs_py-0.1.0/src/solid_jobs/publication_barrier.py +41 -0
- solid_jobs_py-0.1.0/src/solid_jobs/publisher.py +189 -0
- solid_jobs_py-0.1.0/src/solid_jobs/recovery.py +88 -0
- solid_jobs_py-0.1.0/src/solid_jobs/settings.py +32 -0
- solid_jobs_py-0.1.0/src/solid_jobs/solid_redis_gateway.rb +106 -0
- solid_jobs_py-0.1.0/src/solid_jobs/task.py +134 -0
- solid_jobs_py-0.1.0/src/solid_jobs/timer.py +58 -0
- solid_jobs_py-0.1.0/src/solid_jobs/utilities.py +65 -0
- solid_jobs_py-0.1.0/src/solid_jobs/version.py +1 -0
- solid_jobs_py-0.1.0/src/solid_jobs/worker.py +64 -0
- solid_jobs_py-0.1.0/src/solid_jobs_py.egg-info/PKG-INFO +231 -0
- solid_jobs_py-0.1.0/src/solid_jobs_py.egg-info/SOURCES.txt +40 -0
- solid_jobs_py-0.1.0/src/solid_jobs_py.egg-info/dependency_links.txt +1 -0
- solid_jobs_py-0.1.0/src/solid_jobs_py.egg-info/entry_points.txt +2 -0
- solid_jobs_py-0.1.0/src/solid_jobs_py.egg-info/top_level.txt +1 -0
- solid_jobs_py-0.1.0/tests/test_basic.py +49 -0
- solid_jobs_py-0.1.0/tests/test_cli.py +34 -0
- solid_jobs_py-0.1.0/tests/test_conductor.py +55 -0
- solid_jobs_py-0.1.0/tests/test_semantics.py +84 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: solid-jobs-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SolidJobs for Python: Redis task runner on PEP 734 subinterpreters, backed by the Ruby solid-redis gem through a sidecar.
|
|
5
|
+
Requires-Python: >=3.14
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# solid-jobs-py
|
|
9
|
+
|
|
10
|
+
Port Python de [solid-jobs](../solid-jobs) (Ruby) : jobs fiables sur Redis, exécutés en parallélisme CPU réel
|
|
11
|
+
par des **sous-interpréteurs Python** (PEP 684 / PEP 734, un GIL par interpréteur).
|
|
12
|
+
|
|
13
|
+
**Redis n'est pas recodé en Python.** Tout l'accès Redis passe par un processus compagnon Ruby
|
|
14
|
+
(*sidecar*) qui embarque la gemme [`solid-redis`](https://rubygems.org/gems/solid-redis).
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
[ Processus Python ]
|
|
18
|
+
|-- Subinterpreter 1 (GIL isolé) --\
|
|
19
|
+
|-- Subinterpreter 2 (GIL isolé) --+--> Socket Unix (0600) --> Sidecar Ruby (solid-redis) --> Redis
|
|
20
|
+
|-- Subinterpreter N (GIL isolé) --/ JSON lines
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Prérequis
|
|
24
|
+
|
|
25
|
+
- Python >= 3.14 (`concurrent.interpreters`)
|
|
26
|
+
- Ruby + `gem install solid-redis` (>= 1.0.13) ; `bundle install` fonctionne aussi via le `Gemfile`
|
|
27
|
+
- Un serveur Redis
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pip install . # embarque solid_redis_gateway.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Utilisation
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# myapp/jobs.py (doit être importable par son nom de module dans les workers, pas __main__)
|
|
39
|
+
from solid_jobs import Task
|
|
40
|
+
|
|
41
|
+
class RecalculateAccount(Task, channel="critical", max_failures=10):
|
|
42
|
+
def execute_task(self, account_id):
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def retry_delay(cls, failure_count, error, envelope):
|
|
47
|
+
return 5.0 # secondes, "drop", "archive" ou None (backoff par défaut)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from myapp.jobs import RecalculateAccount
|
|
52
|
+
|
|
53
|
+
RecalculateAccount.enqueue(42)
|
|
54
|
+
RecalculateAccount.enqueue_many([[1], [2], [3]])
|
|
55
|
+
RecalculateAccount.enqueue_after(30, 42)
|
|
56
|
+
RecalculateAccount.schedule_at(datetime_or_timestamp, 42)
|
|
57
|
+
RecalculateAccount.with_options(channel="low").enqueue(42)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Exécuter les workers :
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
REDIS_URL=redis://127.0.0.1:6379/0 solid-jobs -r myapp.jobs -c 4 --channel critical,3 --channel default -t 25
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Options : `-r/--require`, `-C/--config` (TOML ou JSON), `-c/--concurrency`, `--channel nom[,poids]`,
|
|
67
|
+
`-t/--timeout`, `--redis-url`. Signaux : `SIGTERM`/`SIGINT` arrêt gracieux, `SIGTSTP` quiet, `SIGTTIN` dump des threads.
|
|
68
|
+
|
|
69
|
+
Variables d'environnement : `REDIS_URL`, `SOLID_JOBS_RUBY` (commande ruby, ex. `bundle exec ruby`),
|
|
70
|
+
`SOLID_JOBS_GATEWAY_SOCKET` (utiliser un sidecar déjà lancé).
|
|
71
|
+
|
|
72
|
+
Tests : `Task.execute(...)` exécute en ligne ; `with lab.testing("capture"):` capture au lieu d'écrire dans Redis.
|
|
73
|
+
|
|
74
|
+
## Garanties (identiques à la version Ruby)
|
|
75
|
+
|
|
76
|
+
Même clavier de clés Redis (`solid_jobs:*`) et mêmes scripts Lua : réservation fiable (liste *claimed* par worker),
|
|
77
|
+
ack/requeue protégés par un jeton de claim (fencing), récupération des claims de nœuds morts, retries avec backoff,
|
|
78
|
+
archive des échecs, tâches planifiées, barrière de publication, `IntegrityCheck`.
|
|
79
|
+
|
|
80
|
+
## Protocole du sidecar
|
|
81
|
+
|
|
82
|
+
Une requête JSON par ligne, une réponse par ligne :
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
{"command": "RPOP", "args": ["queue"]} -> {"status": "OK", "data": ...}
|
|
86
|
+
{"pipeline": [["SET","a",1],["INCR","a"]]} -> {"status": "OK", "data": [...]}
|
|
87
|
+
erreur -> {"status": "ERROR", "error": {"class": "...", "message": "..."}}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Un thread et un client `SolidRedis::Client` par connexion. Socket créée en `0600` dans un répertoire `0700`.
|
|
91
|
+
Le sidecar s'arrête quand le parent meurt (EOF sur stdin).
|
|
92
|
+
|
|
93
|
+
## Écarts par rapport au RFC
|
|
94
|
+
|
|
95
|
+
- `require "solid_redis"` (et non `solid-redis`) ; `SolidRedis.execute` n'existe pas : le sidecar utilise l'API réelle
|
|
96
|
+
`SolidRedis.config(url:).new_client` + `call_v` / `pipelined`.
|
|
97
|
+
- Gateway multi-thread (une connexion = un thread) au lieu d'une boucle `accept` série, sinon un worker bloquerait les autres.
|
|
98
|
+
- Réservation via scripts Lua (`EVALSHA`) et non un simple `RPOP`.
|
|
99
|
+
|
|
100
|
+
## Volontairement non porté
|
|
101
|
+
|
|
102
|
+
Adaptateurs Rails/ActiveJob, warm pool, code spécifique Ractor, télémétrie SolidTrace (`observations.rb`).
|
|
103
|
+
Les hooks d'instrumentation des jobs sont conservés (l'instrumenteur doit être une classe sans argument).
|
|
104
|
+
|
|
105
|
+
## Benchmark : Celery vs solid-jobs-py
|
|
106
|
+
|
|
107
|
+
Environnement : Python 3.14.7, 12 CPU logiques (Apple Silicon), Celery 5.6.3, solid-jobs-py 0.1.0 + sidecar Ruby `solid-redis`,
|
|
108
|
+
Redis local isolé. Méthodologie identique au benchmark Sidekiq/SolidJobs (Ruby) : worker neuf par mesure, ordre des systèmes
|
|
109
|
+
alterné, **médiane de 6 répétitions de 1 000 jobs**, débit mesuré du premier au dernier job d'une file pré-remplie,
|
|
110
|
+
RSS/CPU de tout l'arbre de processus (sidecar Ruby inclus). Job CPU = boucle entière de 100 000 itérations, job IO = sleep 5 ms,
|
|
111
|
+
`mixed` = alternance des deux. Celery : pool `prefork`, `--prefetch-multiplier=1 -Ofair`, broker Redis, résultats ignorés.
|
|
112
|
+
|
|
113
|
+
| Workload | Conc. | System | Jobs/s | vs Celery | Queue p50 (ms) | Exec p50 (ms) | CPU-s/1,000 jobs | Peak RSS (MiB) | Marginal RSS/unit (MiB) | jobs/s/MiB |
|
|
114
|
+
|---|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
115
|
+
| cpu | 1 | celery | 134 | 1.00x | 4018 | 7.3 | 7.69 | 84 | - | 1.60 |
|
|
116
|
+
| cpu | 1 | solid-jobs-py | 134 | 1.00x | 4091 | 7.1 | 7.21 | 67 | - | 2.01 |
|
|
117
|
+
| cpu | 2 | celery | 261 | 1.00x | 2236 | 7.4 | 7.83 | 110 | 26.5 | 2.37 |
|
|
118
|
+
| cpu | 2 | solid-jobs-py | 263 | 1.01x | 2261 | 7.2 | 7.34 | 77 | 10.6 | 3.40 |
|
|
119
|
+
| cpu | 4 | celery | 520 | 1.00x | 1325 | 7.3 | 7.77 | 164 | 26.7 | 3.17 |
|
|
120
|
+
| cpu | 4 | solid-jobs-py | 501 | 0.96x | 1382 | 7.4 | 7.64 | 99 | 10.7 | 5.08 |
|
|
121
|
+
| cpu | 8 | celery | 957 | 1.00x | 917 | 7.5 | 8.46 | 270 | 26.4 | 3.55 |
|
|
122
|
+
| cpu | 8 | solid-jobs-py | 924 | 0.97x | 933 | 7.7 | 8.21 | 141 | 10.7 | 6.54 |
|
|
123
|
+
| io | 1 | celery | 166 | 1.00x | 3298 | 5.8 | 0.66 | 84 | - | 1.98 |
|
|
124
|
+
| io | 1 | solid-jobs-py | 147 | 0.88x | 3741 | 6.4 | 0.34 | 67 | - | 2.20 |
|
|
125
|
+
| io | 2 | celery | 330 | 1.00x | 1823 | 5.8 | 0.62 | 111 | 26.5 | 2.98 |
|
|
126
|
+
| io | 2 | solid-jobs-py | 295 | 0.89x | 2054 | 6.4 | 0.34 | 78 | 10.9 | 3.80 |
|
|
127
|
+
| io | 4 | celery | 654 | 1.00x | 1101 | 5.8 | 0.54 | 164 | 26.7 | 3.98 |
|
|
128
|
+
| io | 4 | solid-jobs-py | 583 | 0.89x | 1231 | 6.4 | 0.38 | 99 | 10.6 | 5.91 |
|
|
129
|
+
| io | 8 | celery | 1311 | 1.00x | 778 | 5.7 | 0.52 | 270 | 26.5 | 4.85 |
|
|
130
|
+
| io | 8 | solid-jobs-py | 1114 | 0.85x | 837 | 6.4 | 0.45 | 141 | 10.7 | 7.88 |
|
|
131
|
+
| mixed | 1 | celery | 149 | 1.00x | 3660 | 7.3 | 4.13 | 84 | - | 1.77 |
|
|
132
|
+
| mixed | 1 | solid-jobs-py | 138 | 0.93x | 3963 | 7.4 | 3.88 | 67 | - | 2.06 |
|
|
133
|
+
| mixed | 2 | celery | 294 | 1.00x | 2024 | 7.3 | 4.16 | 111 | 26.5 | 2.66 |
|
|
134
|
+
| mixed | 2 | solid-jobs-py | 280 | 0.95x | 2147 | 7.1 | 3.77 | 78 | 10.7 | 3.61 |
|
|
135
|
+
| mixed | 4 | celery | 582 | 1.00x | 1211 | 7.3 | 4.15 | 164 | 26.7 | 3.55 |
|
|
136
|
+
| mixed | 4 | solid-jobs-py | 553 | 0.95x | 1279 | 7.2 | 3.84 | 99 | 10.7 | 5.59 |
|
|
137
|
+
| mixed | 8 | celery | 1133 | 1.00x | 838 | 7.4 | 4.20 | 271 | 26.7 | 4.18 |
|
|
138
|
+
| mixed | 8 | solid-jobs-py | 1059 | 0.93x | 863 | 7.4 | 4.03 | 141 | 10.5 | 7.52 |
|
|
139
|
+
|
|
140
|
+
Rapport complet (enqueue, p95/p99, CPU %, efficacité de scaling, CSV) et code :
|
|
141
|
+
`../benchmark_sidekiq_solid-jobs/celery_vs_solid_jobs_py/` (`python bench.py`).
|
|
142
|
+
|
|
143
|
+
**Lecture des résultats (honnête) :**
|
|
144
|
+
|
|
145
|
+
- **Débit : parité en CPU, Celery devant de 5 à 15 % en IO/mixed, mais ce n'est pas un coût de solid-jobs-py.**
|
|
146
|
+
Celery fork déjà ses workers, donc les sous-interpréteurs n'ajoutent pas de débit CPU. L'écart IO vient de `time.sleep(5 ms)`
|
|
147
|
+
(la « charge IO » du benchmark) : sous macOS il dure ~5,6 ms dans un enfant forké (Celery prefork) contre ~6,2 ms dans le
|
|
148
|
+
processus principal, un thread ou un sous-interpréteur (mesuré hors de tout framework). C'est l'écart de ~0,6 ms/job observé.
|
|
149
|
+
- **Surcoût du framework : plus faible que Celery.** Avec des jobs vides (`noop`, 5 000 jobs, médiane de 4 runs), après
|
|
150
|
+
l'optimisation « ack + réservation en un aller-retour » (ci-dessous) : 1 worker 4 481 vs 2 309 jobs/s (1,94x),
|
|
151
|
+
4 workers 7 640 vs 2 494 jobs/s (3,06x).
|
|
152
|
+
- **Optimisations testées** (avant → après, même machine, médianes) :
|
|
153
|
+
- *Ack fusionné avec la réservation suivante* (implémenté) : l'ack part dans le même pipeline que la prochaine réservation,
|
|
154
|
+
soit 1 aller-retour Redis par job au lieu de 2, avec le même fencing. `noop` : 3 697 → 4 481 jobs/s (+21 %) à c=1,
|
|
155
|
+
5 963 → 7 640 jobs/s (+28 %) à c=4. En IO/mixed le gain (~50 µs sur ~6 ms) est sous le bruit de mesure :
|
|
156
|
+
IO c=8 1 114 → 1 133, mixed c=8 1 059 → 1 095 jobs/s ; Celery reste devant en IO (0,86–0,91x).
|
|
157
|
+
- *Script Lua unique* : déjà en place (réservation + jeton de fencing + claim en un seul script).
|
|
158
|
+
- *MessagePack* : **rejeté**. Dans un sous-interpréteur, `msgpack` retombe sur son implémentation Python pure
|
|
159
|
+
(extension C non utilisée) : ~26 µs par aller-retour contre ~6 µs pour le `json` C de la bibliothèque standard.
|
|
160
|
+
- **Mémoire : net avantage solid-jobs-py.** À concurrence 8 : ~141 MiB contre ~270 MiB (environ -48 %), soit ~10,7 MiB par
|
|
161
|
+
worker supplémentaire contre ~26,5 MiB pour un process forké. Résultat : 1,1 à 1,8x plus de jobs/s par MiB.
|
|
162
|
+
Cela confirme l'économie mémoire (~40 %) annoncée dans le RFC.
|
|
163
|
+
- **Fiabilité :** 144 mesures, toutes à 1 000/1 000 jobs exécutés : zéro erreur, zéro doublon. Ces garanties (réservation avec jetons
|
|
164
|
+
de claim / fencing, récupération des nœuds morts) ne sont pas mesurées ici ; seul le débit, la latence et la mémoire le sont.
|
|
165
|
+
- Aucune latence d'exécution n'est dégradée : p50 d'exécution identique (~7 ms CPU), ~0,6 ms de plus en IO.
|
|
166
|
+
- Benchmark local synthétique, non une prédiction de capacité applicative. Celery tourne ici avec le start method `fork`
|
|
167
|
+
forcé (macOS utilise `spawn` par défaut, non supporté par son pool prefork) et `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`.
|
|
168
|
+
|
|
169
|
+
Ce benchmark a aussi mis en évidence deux défauts corrigés dans solid-jobs-py : `interpreters.Queue.get(timeout=…)` fait de
|
|
170
|
+
l'attente active en Python 3.14 (remplacé par `get_nowait()` + sommeil) et une course au démarrage où la récupération pouvait remettre
|
|
171
|
+
en file les claims du nœud avant son premier heartbeat (désormais publié avant le démarrage des workers).
|
|
172
|
+
|
|
173
|
+
### Stress (concurrence 4 / 8 / 16)
|
|
174
|
+
|
|
175
|
+
5000 jobs, 3 répétitions, médianes (`results/stress-c4-8-16.md`). Débit en jobs/s, mémoire = pic RSS de l'arbre de processus.
|
|
176
|
+
|
|
177
|
+
| Charge | c | Celery | solid-jobs-py | Ratio | RSS Celery | RSS solid-jobs-py |
|
|
178
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
179
|
+
| CPU | 4 | 526 | 502 | 0,95x | 165 MiB | 99 MiB |
|
|
180
|
+
| CPU | 8 | 929 | 919 | 0,99x | 273 MiB | 142 MiB |
|
|
181
|
+
| CPU | 16 | 946 | 1023 | 1,08x | 467 MiB | 228 MiB |
|
|
182
|
+
| IO | 4 | 664 | 590 | 0,89x | 164 MiB | 99 MiB |
|
|
183
|
+
| IO | 8 | 1336 | 1154 | 0,86x | 272 MiB | 142 MiB |
|
|
184
|
+
| IO | 16 | 2170 | 2153 | 0,99x | 483 MiB | 226 MiB |
|
|
185
|
+
| Mixed | 4 | 589 | 560 | 0,95x | 165 MiB | 100 MiB |
|
|
186
|
+
| Mixed | 8 | 1163 | 1097 | 0,94x | 272 MiB | 143 MiB |
|
|
187
|
+
| Mixed | 16 | 1796 | 1877 | 1,04x | 487 MiB | 227 MiB |
|
|
188
|
+
|
|
189
|
+
Celery garde l'avantage en IO à faible concurrence (artefact du `sleep` en sous-interpréteur, voir plus haut) ; à c=16
|
|
190
|
+
solid-jobs-py rattrape ou dépasse Celery en CPU et en mixed, avec environ moitié moins de mémoire. Aucun job perdu.
|
|
191
|
+
|
|
192
|
+
### Stabilité (5 minutes par système)
|
|
193
|
+
|
|
194
|
+
4 workers, jobs CPU/IO mélangés, file maintenue à ~1000, un échantillon toutes les 10 s (`stability.py`, durée via
|
|
195
|
+
`BENCHMARK_STABILITY_DURATION`, 1800 s par défaut comme la version Ruby). Les résultats ci-dessous sont des runs courts
|
|
196
|
+
de 300 s, pas de 30 min.
|
|
197
|
+
|
|
198
|
+
| Système | Jobs/s médian | Jobs/s min | RSS début → fin | Pente RSS | Jobs terminés |
|
|
199
|
+
|---|---:|---:|---:|---:|---:|
|
|
200
|
+
| Celery | 590 | 569 | 165 → 165 MiB | +1,3 MiB/h | 176 734 |
|
|
201
|
+
| solid-jobs-py | 556 | 536 | 100 → 100 MiB | +3,5 MiB/h | 166 659 |
|
|
202
|
+
|
|
203
|
+
Débit et mémoire sont plats sur les deux systèmes : pas de fuite visible sur 5 min (une conclusion sur la durée
|
|
204
|
+
demande le run complet de 30 min). Débit mixed à c=4 : Celery ~6 % devant.
|
|
205
|
+
|
|
206
|
+
Commandes : `BENCHMARK_CONCURRENCY=4,8,16 BENCHMARK_REPETITIONS=3 BENCHMARK_JOBS=5000 BENCHMARK_NAME=stress-c4-8-16 .venv/bin/python bench.py`
|
|
207
|
+
puis `BENCHMARK_STABILITY_DURATION=300 .venv/bin/python stability.py`.
|
|
208
|
+
|
|
209
|
+
### Récapitulatif
|
|
210
|
+
|
|
211
|
+
| Axe | Résultat (solid-jobs-py vs Celery) |
|
|
212
|
+
|---|---|
|
|
213
|
+
| Débit CPU | Parité (0,95x à 1,08x selon la concurrence) |
|
|
214
|
+
| Débit mixed | 0,93–0,96x à c ≤ 8, 1,04x à c=16 |
|
|
215
|
+
| Débit IO | Celery devant de 5 à 14 % à c ≤ 8 (artefact du `sleep` en sous-interpréteur), parité à c=16 |
|
|
216
|
+
| Surcoût du framework (`noop`) | 1,94x (c=1) à 3,06x (c=4) plus rapide |
|
|
217
|
+
| Mémoire | environ −48 % (141 vs 270 MiB à c=8), 10,7 vs 26,5 MiB par worker |
|
|
218
|
+
| Stabilité (5 min) | Débit et RSS plats pour les deux, aucun job perdu ; run de 30 min non fait |
|
|
219
|
+
| Optimisations | Ack fusionné avec la réservation : fait (+21 à +28 % en `noop`). Lua unique : déjà en place. MessagePack : rejeté (plus lent en sous-interpréteur) |
|
|
220
|
+
|
|
221
|
+
En résumé : solid-jobs-py égale Celery en CPU et mixed, le dépasse nettement en surcoût et en mémoire, et reste derrière
|
|
222
|
+
de quelques pourcents en IO à faible concurrence.
|
|
223
|
+
|
|
224
|
+
## Développement
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
redis-server --port 6390 --save "" --appendonly no &
|
|
228
|
+
REDIS_URL=redis://127.0.0.1:6390/15 PYTHONPATH=src python3 -m unittest discover -s tests -t .
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Les tests utilisent `FLUSHDB` sur la base indiquée : n'utilisez pas une base de production.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# solid-jobs-py
|
|
2
|
+
|
|
3
|
+
Port Python de [solid-jobs](../solid-jobs) (Ruby) : jobs fiables sur Redis, exécutés en parallélisme CPU réel
|
|
4
|
+
par des **sous-interpréteurs Python** (PEP 684 / PEP 734, un GIL par interpréteur).
|
|
5
|
+
|
|
6
|
+
**Redis n'est pas recodé en Python.** Tout l'accès Redis passe par un processus compagnon Ruby
|
|
7
|
+
(*sidecar*) qui embarque la gemme [`solid-redis`](https://rubygems.org/gems/solid-redis).
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[ Processus Python ]
|
|
11
|
+
|-- Subinterpreter 1 (GIL isolé) --\
|
|
12
|
+
|-- Subinterpreter 2 (GIL isolé) --+--> Socket Unix (0600) --> Sidecar Ruby (solid-redis) --> Redis
|
|
13
|
+
|-- Subinterpreter N (GIL isolé) --/ JSON lines
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Prérequis
|
|
17
|
+
|
|
18
|
+
- Python >= 3.14 (`concurrent.interpreters`)
|
|
19
|
+
- Ruby + `gem install solid-redis` (>= 1.0.13) ; `bundle install` fonctionne aussi via le `Gemfile`
|
|
20
|
+
- Un serveur Redis
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pip install . # embarque solid_redis_gateway.rb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Utilisation
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# myapp/jobs.py (doit être importable par son nom de module dans les workers, pas __main__)
|
|
32
|
+
from solid_jobs import Task
|
|
33
|
+
|
|
34
|
+
class RecalculateAccount(Task, channel="critical", max_failures=10):
|
|
35
|
+
def execute_task(self, account_id):
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def retry_delay(cls, failure_count, error, envelope):
|
|
40
|
+
return 5.0 # secondes, "drop", "archive" ou None (backoff par défaut)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from myapp.jobs import RecalculateAccount
|
|
45
|
+
|
|
46
|
+
RecalculateAccount.enqueue(42)
|
|
47
|
+
RecalculateAccount.enqueue_many([[1], [2], [3]])
|
|
48
|
+
RecalculateAccount.enqueue_after(30, 42)
|
|
49
|
+
RecalculateAccount.schedule_at(datetime_or_timestamp, 42)
|
|
50
|
+
RecalculateAccount.with_options(channel="low").enqueue(42)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Exécuter les workers :
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
REDIS_URL=redis://127.0.0.1:6379/0 solid-jobs -r myapp.jobs -c 4 --channel critical,3 --channel default -t 25
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Options : `-r/--require`, `-C/--config` (TOML ou JSON), `-c/--concurrency`, `--channel nom[,poids]`,
|
|
60
|
+
`-t/--timeout`, `--redis-url`. Signaux : `SIGTERM`/`SIGINT` arrêt gracieux, `SIGTSTP` quiet, `SIGTTIN` dump des threads.
|
|
61
|
+
|
|
62
|
+
Variables d'environnement : `REDIS_URL`, `SOLID_JOBS_RUBY` (commande ruby, ex. `bundle exec ruby`),
|
|
63
|
+
`SOLID_JOBS_GATEWAY_SOCKET` (utiliser un sidecar déjà lancé).
|
|
64
|
+
|
|
65
|
+
Tests : `Task.execute(...)` exécute en ligne ; `with lab.testing("capture"):` capture au lieu d'écrire dans Redis.
|
|
66
|
+
|
|
67
|
+
## Garanties (identiques à la version Ruby)
|
|
68
|
+
|
|
69
|
+
Même clavier de clés Redis (`solid_jobs:*`) et mêmes scripts Lua : réservation fiable (liste *claimed* par worker),
|
|
70
|
+
ack/requeue protégés par un jeton de claim (fencing), récupération des claims de nœuds morts, retries avec backoff,
|
|
71
|
+
archive des échecs, tâches planifiées, barrière de publication, `IntegrityCheck`.
|
|
72
|
+
|
|
73
|
+
## Protocole du sidecar
|
|
74
|
+
|
|
75
|
+
Une requête JSON par ligne, une réponse par ligne :
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
{"command": "RPOP", "args": ["queue"]} -> {"status": "OK", "data": ...}
|
|
79
|
+
{"pipeline": [["SET","a",1],["INCR","a"]]} -> {"status": "OK", "data": [...]}
|
|
80
|
+
erreur -> {"status": "ERROR", "error": {"class": "...", "message": "..."}}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Un thread et un client `SolidRedis::Client` par connexion. Socket créée en `0600` dans un répertoire `0700`.
|
|
84
|
+
Le sidecar s'arrête quand le parent meurt (EOF sur stdin).
|
|
85
|
+
|
|
86
|
+
## Écarts par rapport au RFC
|
|
87
|
+
|
|
88
|
+
- `require "solid_redis"` (et non `solid-redis`) ; `SolidRedis.execute` n'existe pas : le sidecar utilise l'API réelle
|
|
89
|
+
`SolidRedis.config(url:).new_client` + `call_v` / `pipelined`.
|
|
90
|
+
- Gateway multi-thread (une connexion = un thread) au lieu d'une boucle `accept` série, sinon un worker bloquerait les autres.
|
|
91
|
+
- Réservation via scripts Lua (`EVALSHA`) et non un simple `RPOP`.
|
|
92
|
+
|
|
93
|
+
## Volontairement non porté
|
|
94
|
+
|
|
95
|
+
Adaptateurs Rails/ActiveJob, warm pool, code spécifique Ractor, télémétrie SolidTrace (`observations.rb`).
|
|
96
|
+
Les hooks d'instrumentation des jobs sont conservés (l'instrumenteur doit être une classe sans argument).
|
|
97
|
+
|
|
98
|
+
## Benchmark : Celery vs solid-jobs-py
|
|
99
|
+
|
|
100
|
+
Environnement : Python 3.14.7, 12 CPU logiques (Apple Silicon), Celery 5.6.3, solid-jobs-py 0.1.0 + sidecar Ruby `solid-redis`,
|
|
101
|
+
Redis local isolé. Méthodologie identique au benchmark Sidekiq/SolidJobs (Ruby) : worker neuf par mesure, ordre des systèmes
|
|
102
|
+
alterné, **médiane de 6 répétitions de 1 000 jobs**, débit mesuré du premier au dernier job d'une file pré-remplie,
|
|
103
|
+
RSS/CPU de tout l'arbre de processus (sidecar Ruby inclus). Job CPU = boucle entière de 100 000 itérations, job IO = sleep 5 ms,
|
|
104
|
+
`mixed` = alternance des deux. Celery : pool `prefork`, `--prefetch-multiplier=1 -Ofair`, broker Redis, résultats ignorés.
|
|
105
|
+
|
|
106
|
+
| Workload | Conc. | System | Jobs/s | vs Celery | Queue p50 (ms) | Exec p50 (ms) | CPU-s/1,000 jobs | Peak RSS (MiB) | Marginal RSS/unit (MiB) | jobs/s/MiB |
|
|
107
|
+
|---|---:|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
108
|
+
| cpu | 1 | celery | 134 | 1.00x | 4018 | 7.3 | 7.69 | 84 | - | 1.60 |
|
|
109
|
+
| cpu | 1 | solid-jobs-py | 134 | 1.00x | 4091 | 7.1 | 7.21 | 67 | - | 2.01 |
|
|
110
|
+
| cpu | 2 | celery | 261 | 1.00x | 2236 | 7.4 | 7.83 | 110 | 26.5 | 2.37 |
|
|
111
|
+
| cpu | 2 | solid-jobs-py | 263 | 1.01x | 2261 | 7.2 | 7.34 | 77 | 10.6 | 3.40 |
|
|
112
|
+
| cpu | 4 | celery | 520 | 1.00x | 1325 | 7.3 | 7.77 | 164 | 26.7 | 3.17 |
|
|
113
|
+
| cpu | 4 | solid-jobs-py | 501 | 0.96x | 1382 | 7.4 | 7.64 | 99 | 10.7 | 5.08 |
|
|
114
|
+
| cpu | 8 | celery | 957 | 1.00x | 917 | 7.5 | 8.46 | 270 | 26.4 | 3.55 |
|
|
115
|
+
| cpu | 8 | solid-jobs-py | 924 | 0.97x | 933 | 7.7 | 8.21 | 141 | 10.7 | 6.54 |
|
|
116
|
+
| io | 1 | celery | 166 | 1.00x | 3298 | 5.8 | 0.66 | 84 | - | 1.98 |
|
|
117
|
+
| io | 1 | solid-jobs-py | 147 | 0.88x | 3741 | 6.4 | 0.34 | 67 | - | 2.20 |
|
|
118
|
+
| io | 2 | celery | 330 | 1.00x | 1823 | 5.8 | 0.62 | 111 | 26.5 | 2.98 |
|
|
119
|
+
| io | 2 | solid-jobs-py | 295 | 0.89x | 2054 | 6.4 | 0.34 | 78 | 10.9 | 3.80 |
|
|
120
|
+
| io | 4 | celery | 654 | 1.00x | 1101 | 5.8 | 0.54 | 164 | 26.7 | 3.98 |
|
|
121
|
+
| io | 4 | solid-jobs-py | 583 | 0.89x | 1231 | 6.4 | 0.38 | 99 | 10.6 | 5.91 |
|
|
122
|
+
| io | 8 | celery | 1311 | 1.00x | 778 | 5.7 | 0.52 | 270 | 26.5 | 4.85 |
|
|
123
|
+
| io | 8 | solid-jobs-py | 1114 | 0.85x | 837 | 6.4 | 0.45 | 141 | 10.7 | 7.88 |
|
|
124
|
+
| mixed | 1 | celery | 149 | 1.00x | 3660 | 7.3 | 4.13 | 84 | - | 1.77 |
|
|
125
|
+
| mixed | 1 | solid-jobs-py | 138 | 0.93x | 3963 | 7.4 | 3.88 | 67 | - | 2.06 |
|
|
126
|
+
| mixed | 2 | celery | 294 | 1.00x | 2024 | 7.3 | 4.16 | 111 | 26.5 | 2.66 |
|
|
127
|
+
| mixed | 2 | solid-jobs-py | 280 | 0.95x | 2147 | 7.1 | 3.77 | 78 | 10.7 | 3.61 |
|
|
128
|
+
| mixed | 4 | celery | 582 | 1.00x | 1211 | 7.3 | 4.15 | 164 | 26.7 | 3.55 |
|
|
129
|
+
| mixed | 4 | solid-jobs-py | 553 | 0.95x | 1279 | 7.2 | 3.84 | 99 | 10.7 | 5.59 |
|
|
130
|
+
| mixed | 8 | celery | 1133 | 1.00x | 838 | 7.4 | 4.20 | 271 | 26.7 | 4.18 |
|
|
131
|
+
| mixed | 8 | solid-jobs-py | 1059 | 0.93x | 863 | 7.4 | 4.03 | 141 | 10.5 | 7.52 |
|
|
132
|
+
|
|
133
|
+
Rapport complet (enqueue, p95/p99, CPU %, efficacité de scaling, CSV) et code :
|
|
134
|
+
`../benchmark_sidekiq_solid-jobs/celery_vs_solid_jobs_py/` (`python bench.py`).
|
|
135
|
+
|
|
136
|
+
**Lecture des résultats (honnête) :**
|
|
137
|
+
|
|
138
|
+
- **Débit : parité en CPU, Celery devant de 5 à 15 % en IO/mixed, mais ce n'est pas un coût de solid-jobs-py.**
|
|
139
|
+
Celery fork déjà ses workers, donc les sous-interpréteurs n'ajoutent pas de débit CPU. L'écart IO vient de `time.sleep(5 ms)`
|
|
140
|
+
(la « charge IO » du benchmark) : sous macOS il dure ~5,6 ms dans un enfant forké (Celery prefork) contre ~6,2 ms dans le
|
|
141
|
+
processus principal, un thread ou un sous-interpréteur (mesuré hors de tout framework). C'est l'écart de ~0,6 ms/job observé.
|
|
142
|
+
- **Surcoût du framework : plus faible que Celery.** Avec des jobs vides (`noop`, 5 000 jobs, médiane de 4 runs), après
|
|
143
|
+
l'optimisation « ack + réservation en un aller-retour » (ci-dessous) : 1 worker 4 481 vs 2 309 jobs/s (1,94x),
|
|
144
|
+
4 workers 7 640 vs 2 494 jobs/s (3,06x).
|
|
145
|
+
- **Optimisations testées** (avant → après, même machine, médianes) :
|
|
146
|
+
- *Ack fusionné avec la réservation suivante* (implémenté) : l'ack part dans le même pipeline que la prochaine réservation,
|
|
147
|
+
soit 1 aller-retour Redis par job au lieu de 2, avec le même fencing. `noop` : 3 697 → 4 481 jobs/s (+21 %) à c=1,
|
|
148
|
+
5 963 → 7 640 jobs/s (+28 %) à c=4. En IO/mixed le gain (~50 µs sur ~6 ms) est sous le bruit de mesure :
|
|
149
|
+
IO c=8 1 114 → 1 133, mixed c=8 1 059 → 1 095 jobs/s ; Celery reste devant en IO (0,86–0,91x).
|
|
150
|
+
- *Script Lua unique* : déjà en place (réservation + jeton de fencing + claim en un seul script).
|
|
151
|
+
- *MessagePack* : **rejeté**. Dans un sous-interpréteur, `msgpack` retombe sur son implémentation Python pure
|
|
152
|
+
(extension C non utilisée) : ~26 µs par aller-retour contre ~6 µs pour le `json` C de la bibliothèque standard.
|
|
153
|
+
- **Mémoire : net avantage solid-jobs-py.** À concurrence 8 : ~141 MiB contre ~270 MiB (environ -48 %), soit ~10,7 MiB par
|
|
154
|
+
worker supplémentaire contre ~26,5 MiB pour un process forké. Résultat : 1,1 à 1,8x plus de jobs/s par MiB.
|
|
155
|
+
Cela confirme l'économie mémoire (~40 %) annoncée dans le RFC.
|
|
156
|
+
- **Fiabilité :** 144 mesures, toutes à 1 000/1 000 jobs exécutés : zéro erreur, zéro doublon. Ces garanties (réservation avec jetons
|
|
157
|
+
de claim / fencing, récupération des nœuds morts) ne sont pas mesurées ici ; seul le débit, la latence et la mémoire le sont.
|
|
158
|
+
- Aucune latence d'exécution n'est dégradée : p50 d'exécution identique (~7 ms CPU), ~0,6 ms de plus en IO.
|
|
159
|
+
- Benchmark local synthétique, non une prédiction de capacité applicative. Celery tourne ici avec le start method `fork`
|
|
160
|
+
forcé (macOS utilise `spawn` par défaut, non supporté par son pool prefork) et `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`.
|
|
161
|
+
|
|
162
|
+
Ce benchmark a aussi mis en évidence deux défauts corrigés dans solid-jobs-py : `interpreters.Queue.get(timeout=…)` fait de
|
|
163
|
+
l'attente active en Python 3.14 (remplacé par `get_nowait()` + sommeil) et une course au démarrage où la récupération pouvait remettre
|
|
164
|
+
en file les claims du nœud avant son premier heartbeat (désormais publié avant le démarrage des workers).
|
|
165
|
+
|
|
166
|
+
### Stress (concurrence 4 / 8 / 16)
|
|
167
|
+
|
|
168
|
+
5000 jobs, 3 répétitions, médianes (`results/stress-c4-8-16.md`). Débit en jobs/s, mémoire = pic RSS de l'arbre de processus.
|
|
169
|
+
|
|
170
|
+
| Charge | c | Celery | solid-jobs-py | Ratio | RSS Celery | RSS solid-jobs-py |
|
|
171
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
172
|
+
| CPU | 4 | 526 | 502 | 0,95x | 165 MiB | 99 MiB |
|
|
173
|
+
| CPU | 8 | 929 | 919 | 0,99x | 273 MiB | 142 MiB |
|
|
174
|
+
| CPU | 16 | 946 | 1023 | 1,08x | 467 MiB | 228 MiB |
|
|
175
|
+
| IO | 4 | 664 | 590 | 0,89x | 164 MiB | 99 MiB |
|
|
176
|
+
| IO | 8 | 1336 | 1154 | 0,86x | 272 MiB | 142 MiB |
|
|
177
|
+
| IO | 16 | 2170 | 2153 | 0,99x | 483 MiB | 226 MiB |
|
|
178
|
+
| Mixed | 4 | 589 | 560 | 0,95x | 165 MiB | 100 MiB |
|
|
179
|
+
| Mixed | 8 | 1163 | 1097 | 0,94x | 272 MiB | 143 MiB |
|
|
180
|
+
| Mixed | 16 | 1796 | 1877 | 1,04x | 487 MiB | 227 MiB |
|
|
181
|
+
|
|
182
|
+
Celery garde l'avantage en IO à faible concurrence (artefact du `sleep` en sous-interpréteur, voir plus haut) ; à c=16
|
|
183
|
+
solid-jobs-py rattrape ou dépasse Celery en CPU et en mixed, avec environ moitié moins de mémoire. Aucun job perdu.
|
|
184
|
+
|
|
185
|
+
### Stabilité (5 minutes par système)
|
|
186
|
+
|
|
187
|
+
4 workers, jobs CPU/IO mélangés, file maintenue à ~1000, un échantillon toutes les 10 s (`stability.py`, durée via
|
|
188
|
+
`BENCHMARK_STABILITY_DURATION`, 1800 s par défaut comme la version Ruby). Les résultats ci-dessous sont des runs courts
|
|
189
|
+
de 300 s, pas de 30 min.
|
|
190
|
+
|
|
191
|
+
| Système | Jobs/s médian | Jobs/s min | RSS début → fin | Pente RSS | Jobs terminés |
|
|
192
|
+
|---|---:|---:|---:|---:|---:|
|
|
193
|
+
| Celery | 590 | 569 | 165 → 165 MiB | +1,3 MiB/h | 176 734 |
|
|
194
|
+
| solid-jobs-py | 556 | 536 | 100 → 100 MiB | +3,5 MiB/h | 166 659 |
|
|
195
|
+
|
|
196
|
+
Débit et mémoire sont plats sur les deux systèmes : pas de fuite visible sur 5 min (une conclusion sur la durée
|
|
197
|
+
demande le run complet de 30 min). Débit mixed à c=4 : Celery ~6 % devant.
|
|
198
|
+
|
|
199
|
+
Commandes : `BENCHMARK_CONCURRENCY=4,8,16 BENCHMARK_REPETITIONS=3 BENCHMARK_JOBS=5000 BENCHMARK_NAME=stress-c4-8-16 .venv/bin/python bench.py`
|
|
200
|
+
puis `BENCHMARK_STABILITY_DURATION=300 .venv/bin/python stability.py`.
|
|
201
|
+
|
|
202
|
+
### Récapitulatif
|
|
203
|
+
|
|
204
|
+
| Axe | Résultat (solid-jobs-py vs Celery) |
|
|
205
|
+
|---|---|
|
|
206
|
+
| Débit CPU | Parité (0,95x à 1,08x selon la concurrence) |
|
|
207
|
+
| Débit mixed | 0,93–0,96x à c ≤ 8, 1,04x à c=16 |
|
|
208
|
+
| Débit IO | Celery devant de 5 à 14 % à c ≤ 8 (artefact du `sleep` en sous-interpréteur), parité à c=16 |
|
|
209
|
+
| Surcoût du framework (`noop`) | 1,94x (c=1) à 3,06x (c=4) plus rapide |
|
|
210
|
+
| Mémoire | environ −48 % (141 vs 270 MiB à c=8), 10,7 vs 26,5 MiB par worker |
|
|
211
|
+
| Stabilité (5 min) | Débit et RSS plats pour les deux, aucun job perdu ; run de 30 min non fait |
|
|
212
|
+
| Optimisations | Ack fusionné avec la réservation : fait (+21 à +28 % en `noop`). Lua unique : déjà en place. MessagePack : rejeté (plus lent en sous-interpréteur) |
|
|
213
|
+
|
|
214
|
+
En résumé : solid-jobs-py égale Celery en CPU et mixed, le dépasse nettement en surcoût et en mémoire, et reste derrière
|
|
215
|
+
de quelques pourcents en IO à faible concurrence.
|
|
216
|
+
|
|
217
|
+
## Développement
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
redis-server --port 6390 --save "" --appendonly no &
|
|
221
|
+
REDIS_URL=redis://127.0.0.1:6390/15 PYTHONPATH=src python3 -m unittest discover -s tests -t .
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Les tests utilisent `FLUSHDB` sur la base indiquée : n'utilisez pas une base de production.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "solid-jobs-py"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SolidJobs for Python: Redis task runner on PEP 734 subinterpreters, backed by the Ruby solid-redis gem through a sidecar."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
solid-jobs = "solid_jobs.console:main"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.packages.find]
|
|
17
|
+
where = ["src"]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.package-data]
|
|
20
|
+
solid_jobs = ["*.rb"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""SolidJobs for Python: Redis task runner on PEP 734 subinterpreters.
|
|
2
|
+
|
|
3
|
+
Redis access goes through the Ruby ``solid-redis`` gem, run as a sidecar.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from . import lab
|
|
7
|
+
from .blueprint import Blueprint
|
|
8
|
+
from .catalog import (Channel, Claims, Counters, DiscardedTasks, Execution, Executions, Node, Nodes,
|
|
9
|
+
PlannedTasks, RetryingTasks, StoredTask)
|
|
10
|
+
from .conductor import Conductor
|
|
11
|
+
from .errors import (ConfigurationError, Error, ExecutionHalt, GatewayError, InvalidArgumentError,
|
|
12
|
+
InvalidJobError, NoCapturedTask, RedisCommandError, RedisConnectionError, RedisError)
|
|
13
|
+
from .integrity_check import IntegrityCheck
|
|
14
|
+
from .publisher import Publisher
|
|
15
|
+
from .settings import config, configure, reset, use_config
|
|
16
|
+
from .task import Task
|
|
17
|
+
from .version import VERSION
|
|
18
|
+
|
|
19
|
+
__version__ = VERSION
|
|
20
|
+
testing = lab.testing
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Blueprint", "Channel", "Claims", "Conductor", "ConfigurationError", "Counters", "DiscardedTasks",
|
|
24
|
+
"Error", "Execution", "ExecutionHalt", "Executions", "GatewayError", "IntegrityCheck",
|
|
25
|
+
"InvalidArgumentError", "InvalidJobError", "NoCapturedTask", "Node", "Nodes", "PlannedTasks",
|
|
26
|
+
"Publisher", "RedisCommandError", "RedisConnectionError", "RedisError", "RetryingTasks",
|
|
27
|
+
"StoredTask", "Task", "VERSION", "config", "configure", "lab", "reset", "testing", "use_config",
|
|
28
|
+
]
|