You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Close out the convenience/readability/gap tail from the 2026-06-13 docs
audit; signatures and defaults verified against source.
- P1/P2/P3 subscriber.md: document propagate_inbound_headers, the
standard passthrough kwargs, and a retry-strategy params/defaults table.
- P4/P5 publisher.md: show the broker.publisher() signature; add a
FastAPI-specific callout to the OutboxResponse chained-publish example
(the session must outlive the handler return — vanilla apps call
broker.publish directly).
- P6 index.md: signpost the three observability entries (concept /
reference / step-by-step).
- P7 instrumentation-seams.md: state the recorder "must not block"
constraint; make the event count self-documenting.
- P8 observability.md: add a bundled-adapter wiring snippet.
- P9/P10/P11 comparison.md: bullet the feature wall, add a decision
matrix, give the CDC verdict a falsifiable detail.
- P12/P13 how-it-works.md: flesh out the relay stub; dedupe the
idempotency note.
- P14/P15 testing.md: runnable loop-mode example; drop the inconsistent
pytest.mark.asyncio marker.
- P16 troubleshooting.md: name the missing-timer_id DLQ migration cause.
- P17 basic.md: drop the unexplained max_workers=4.
- P18 relay.md: setup note + OutboxResponse import in the anti-pattern.
All findings (B1–B5, C1–C3, I1–I15, P1–P18) now resolved.
mkdocs build --strict clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|[Writing your own](#vs-writing-your-own-outbox-table-and-worker)| You only ever need the MVP shape | You expect the system to live for years |
11
+
|[CDC / Debezium](#vs-cdc-debezium-logical-replication)| You already need WAL capture, or producers are outside your control | You own the producer and want retry / DLQ / timers in-process |
12
+
|[Kafka txns / Rabbit confirms](#vs-kafka-transactions-or-rabbitmq-publisher-confirms)| The bus is already running and you need bus-scale throughput | Postgres is your only durable store |
13
+
|[Plain `LISTEN/NOTIFY`](#vs-plain-listennotify)| You only need a wake-up, not a delivery guarantee | You need durability, replay, and retry |
14
+
|[Celery / RQ / Dramatiq](#vs-celery-or-rq-dramatiq-with-a-db-backend)| You're modelling ad-hoc background jobs | You're modelling events that must commit with a DB write |
15
+
|[FastStream foreign broker](#vs-faststream-kafkabroker-rabbitbroker-directly)| You have no DB write to commit alongside the publish | You need atomicity with a DB write (use *both*, via [Relay](../usage/relay.md)) |
16
+
8
17
## vs. writing your own outbox table and worker
9
18
10
19
A bespoke outbox is the most common starting point — the pattern itself is
11
20
straightforward, and an MVP can ship in an afternoon. What `faststream-outbox`
12
21
buys you, in concrete terms, is the pile of pieces that turn that MVP into a
13
-
production system: per-row **lease tokens** with a load-bearing invariant
14
-
that any new fetch / terminal path must preserve; the **partial-index
15
-
design** the fetch CTE depends on (without those, the disjunctive WHERE
16
-
clause falls back to seq-scan as the table grows); the **fetch-and-claim
17
-
CTE shape** with `FOR UPDATE SKIP LOCKED` that reclaims both unleased rows
Copy file name to clipboardExpand all lines: docs/introduction/how-it-works.md
+11-2Lines changed: 11 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -178,13 +178,22 @@ you add).
178
178
179
179
## Failure modes
180
180
181
-
-**Handlers must be idempotent.**Crash between commit-of-handler-side-effects and the broker's `DELETE` re-delivers the message.
181
+
-**Handlers must be idempotent.**A crash between the handler's side effect and the broker's `DELETE` re-delivers the message — see [At-least-once delivery](#at-least-once-delivery) above.
182
182
-**Best-effort ordering only.**`FOR UPDATE SKIP LOCKED` does not preserve strict order under concurrent workers. If you need strict per-aggregate ordering, route to a single subscriber and run a single worker.
183
183
-**DLQ is opt-in.** Without `dlq_table=`, terminal failures `DELETE` the row.
184
184
185
185
## Relay to Kafka / RabbitMQ / NATS / Redis
186
186
187
-
> **Relay outbox rows to Kafka / RabbitMQ / NATS / Redis with a single decorator → [Relay tutorial](../tutorials/add-kafka-relay.md).**
187
+
An `OutboxSubscriber` can source a FastStream-native cross-broker chain:
188
+
stack a foreign-broker publisher decorator on the subscriber
189
+
(`@kafka_pub @broker_outbox.subscriber("q")`) and the handler's return
190
+
value is forwarded to the real bus. The outbox row stays the durability
191
+
boundary — the row commits with the domain write, and the relay carries
192
+
at-least-once end to end. If the foreign publish fails, the row is **not**
193
+
nacked through the `retry_strategy`; its lease simply expires and a later
194
+
fetch retries the relay, so a transient bus outage never loses the row.
195
+
196
+
> **Worked end-to-end example → [Relay tutorial](../tutorials/add-kafka-relay.md).**
Copy file name to clipboardExpand all lines: docs/usage/subscriber.md
+18Lines changed: 18 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,6 +89,7 @@ Per-subscriber knobs, passed to `@broker.subscriber("…", …)`:
89
89
|`max_deliveries`|`None` (unbounded) | Total claims (including lease-expiry re-claims) after which the row is dropped without invoking the handler. Defends against handlers that consistently wedge. |
90
90
|`ack_policy`|`AckPolicy.NACK_ON_ERROR`| See [Ack policy](#ack-policy)|
91
91
|`retry_strategy`|`ExponentialRetry(...)`| See [Retry strategies](#retry-strategies)|
92
+
|`propagate_inbound_headers`|`False`| Relay-only. When `True`, fills `Response.headers` from the inbound message *if* the handler returned a `Response` with empty headers (user-set headers always win). See [Relay](./relay.md). |
92
93
93
94
```python
94
95
@broker.subscriber(
@@ -106,6 +107,10 @@ The factory in `subscriber/factory.py` warns or raises on likely-wrong
0 commit comments