Livraison outbox et rejeu¶
L'outbox MIC garantit une livraison at-least-once. Elle ne promet jamais exactly-once. L'ordre par agrégat existe, mais il doit être activé explicitement quand le consommateur ne peut pas accepter une inversion.
Mode de débit par défaut¶
Les stores lisent les événements en attente par priorité puis ordre d'arrivée. Le dispatcher traite le batch séquentiellement et marque chaque événement après publication. Un événement en échec peut donc être dépassé par le suivant.
Avec use_claim=True, un lease place l'événement en traitement. Les leases
expirés sont récupérables après la mort d'un worker, et SKIP LOCKED permet aux
workers SQL de réclamer des ensembles disjoints.
Un crash après publication mais avant mark_dispatched() peut produire un
doublon. Le consommateur doit rester idempotent dans tous les modes.
Ordre par agrégat¶
Activez le mode ordonné sur le dispatcher :
from mic.outbox import AsyncOutboxDispatcher
dispatcher = AsyncOutboxDispatcher(
store=store,
dispatch_fn=publish,
use_claim=True,
lease_ttl_seconds=60,
preserve_aggregate_order=True,
)
La clé d'ordre est le couple (aggregate_type, aggregate_id) déjà porté par
OutboxEvent. En mode ordonné, seule la plus ancienne ligne non terminale de
chaque agrégat est visible :
- une tête
PENDINGen attente de retry bloque ses successeurs ; - une tête
PROCESSINGsous lease les bloque également ; DISPATCHEDetDLQsont terminaux et libèrent la ligne suivante ;- les autres agrégats continuent en parallèle.
L'ordre est (created_at, id) dans un agrégat. Une priorité supérieure sur un
événement plus récent ne lui permet pas de dépasser sa propre tête. Avec
plusieurs workers, combinez toujours ce mode avec use_claim=True.
Cette garantie ne crée ni ordre global entre agrégats, ni exactly-once. Sur une
table volumineuse, l'application doit aussi ajouter et mesurer un index adapté à
son dialecte sur (aggregate_type, aggregate_id, status, created_at, id).
Rejeu temporel¶
Le rejeu reste séparé du dispatcher transactionnel. mic.event_replay lit une
archive immuable JSON ou ClickHouse, filtre par topic et intervalle inclusif,
puis republie dans l'ordre déterministe (created_at, original_id).
mic-events-replay --from-clickhouse http://clickhouse.svc:8123 \
--topic resource.changed \
--from 2026-09-29T08:00:00Z \
--to 2026-09-29T10:00:00Z \
--to-nats nats://nats-0:4222 --nats-stream events
Chaque publication vers le bus porte x-replay: true, un
x-replay-batch-id commun au lot et x-original-event-id comme clé
d'idempotence stable. Sans sink, la commande effectue un dry-run. Une erreur de
publication interrompt le lot ; une reprise peut donc redélivrer son préfixe.
Choix opérationnel¶
| Besoin | Primitive |
|---|---|
| Publication fiable après transaction | OutboxDispatcher + OutboxStore |
| Dispatch concurrent avec reprise après crash | use_claim=True + leases |
| Ordre strict dans chaque agrégat | preserve_aggregate_order=True + claim |
| Reconstruction depuis l'historique | mic.event_replay + archive |
| Exactly-once ou ordre global | Hors garantie MIC ; idempotence et topologie métier requises |
Voir aussi Retry sans idempotence.