diff --git a/docs/adr/0001-metrics-recorders-not-unified.md b/docs/adr/0001-metrics-recorders-not-unified.md index bf19637..8001784 100644 --- a/docs/adr/0001-metrics-recorders-not-unified.md +++ b/docs/adr/0001-metrics-recorders-not-unified.md @@ -1,17 +1,12 @@ -# Metrics recorders stay separate; no shared event→metric table +# Metrics recorders stay separate, with no shared event-to-metric table -**Decision:** the two `MetricsRecorder` adapters (`metrics/prometheus.py`, -`metrics/opentelemetry.py`) keep their own hand-written `__call__` event switches. There is no -declarative event→metric table both adapters consume. - -The two switches branch on the same event names, which reads as duplication, but only the dispatch -ladder is actually shared. The same event maps to a *different number of instruments* per backend — -`dispatched` is three Prometheus operations and an OpenTelemetry no-op — and the same tag is a -labeled counter in one and a span attribute in the other. A `(kind, name, label set)` table would -need a per-backend escape hatch on nearly every row, and deleting it would push the per-event bodies -back exactly where they are now. - -- **A parity contract test**, feeding each event to both adapters, addresses the real worry ("add an - event, forget one adapter") — which is parity, not duplication. Declined for now: the vocabulary - is stable and additive, both adapters are independently tested, and a new event is emitted from a - call site the author is already editing. +The two `MetricsRecorder` adapters, `metrics/prometheus.py` and `metrics/opentelemetry.py`, keep +their own hand-written `__call__` event switches, and no declarative event-to-metric table is shared +between them. Only the dispatch ladder is common: the same event maps to a different number of +instruments per backend, so `dispatched` is three Prometheus operations and an OpenTelemetry no-op, +and the same tag is a labeled counter in one and a span attribute in the other. A +`(kind, name, label set)` table would need a per-backend escape hatch on nearly every row, and +deleting it would push the per-event bodies back exactly where they are now. A parity contract test +feeding each event to both adapters addresses the real worry, which is parity rather than +duplication, and is declined for now because the event vocabulary is stable and additive and both +adapters are independently tested. diff --git a/docs/adr/0002-free-threading-is-compat-only.md b/docs/adr/0002-free-threading-is-compat-only.md index 0fe0d55..868851f 100644 --- a/docs/adr/0002-free-threading-is-compat-only.md +++ b/docs/adr/0002-free-threading-is-compat-only.md @@ -1,33 +1,14 @@ # Free-threading is a compatibility guarantee, not a parallelism redesign -**Decision:** "free-threaded support" means proving that `faststream-outbox` runs correctly on a -free-threaded CPython (3.14t) with the GIL disabled, scoped to "when SQLAlchemy's Cython extensions -are off". It does not mean rearchitecting the subscriber to use multiple CPU cores. Target 3.14t -only, not 3.13t. - -The package is pure-Python asyncio — one event loop, N worker *tasks* rather than OS threads, no -`threading`, lock, or C-extension code of its own — so free-threading changes none of its runtime -semantics and the guarantee costs a CI job, a classifier, and a docs note rather than a source -change. What users need to know is in `docs/introduction/installation.md`: 3.14t support, and that -`DISABLE_SQLALCHEMY_CEXT_RUNTIME=1` is required for a genuinely GIL-free process because -SQLAlchemy's cyextensions do not declare `Py_MOD_GIL_NOT_USED` and re-enable the GIL process-wide on -import. - -- **Exploiting the parallelism** — worker loops across OS threads or multiple event loops — is a - rearchitecture of a deliberately single-loop design; the two-loop subscriber, the lease-token - invariant, and drain-on-stop all assume one loop. Outbox throughput is dominated by Postgres I/O - and lease contention rather than in-process CPU, and scaling today is "run more subscriber - processes", which already uses more cores. Real invariant-breaking risk for an unproven gain. -- **Dropping the GIL-off assertion** and making no GIL claim discards the regression guard that - caught the SQLAlchemy behaviour in the first place. -- **Deferring until SQLAlchemy ships free-thread-safe cyextensions** banks nothing: the guarantee is - true today, the workaround is SQLAlchemy's own documented switch, the upstream timeline is - open-ended, and withholding the classifier leaves 3.14t adopters with no signal. -- **Targeting 3.13t** is not available: the compiled dependencies ship `cp314t` wheels but no - `cp313t` ones, so the full graph will not install. - -**Revisit trigger:** drop `DISABLE_SQLALCHEMY_CEXT_RUNTIME=1` from the `freethreaded` CI job and the -docs caveat once SQLAlchemy's Cython extensions declare `Py_MOD_GIL_NOT_USED`. This is the one part -of the decision that is scheduled work rather than a judgement call — the workaround is upstream's -to remove, and until it does, the env var is load-bearing. Re-run the job's GIL assertion without it -to confirm before removing. Tracked as #160. +Free-threaded support means proving that `faststream-outbox` runs correctly on free-threaded CPython +3.14t with the GIL disabled; it does not mean rearchitecting the subscriber to use multiple cores. +The package is pure-Python asyncio, one event loop with N worker tasks rather than OS threads, so +free-threading changes none of its runtime semantics and the guarantee costs a CI job, a +`Free Threading :: 2 - Beta` classifier, and a docs note rather than a source change. Exploiting the +parallelism was rejected because the two-loop subscriber, the lease-token invariant, and +drain-on-stop all assume one loop, throughput is dominated by Postgres I/O, and scaling today means +running more subscriber processes. The guarantee is bounded by SQLAlchemy: its Cython extensions do +not declare `Py_MOD_GIL_NOT_USED` and re-enable the GIL process-wide on import, so +`DISABLE_SQLALCHEMY_CEXT_RUNTIME=1` is load-bearing until upstream fixes that +([#160](https://github.com/modern-python/faststream-outbox/issues/160)). 3.13t is not a target +because the compiled dependencies ship `cp314t` wheels only.