docs(design): webhook fan-out → River migration plan#398
Merged
Conversation
Scopes moving the last hand-rolled durable background loop — the webhookpub OutboxWorker that fans webhook_events → subscriber_deliveries (Layer 1 → Layer 2) — onto River, completing the webhook triad after the delivery (Layer 2 → 3) migration. Same three-layer in-tx-enqueue pattern as inbound (#391): the trigger writes the webhook_events fact row AND enqueues a webhook_fan_out River job in one tx; a FanOutWorker re-reads, matches, inserts deliveries + enqueues the existing delivery jobs, marks the event terminal. The enqueue seam is the two Outbox methods (PublishTx/PublishBestEffortTx) — so the ~10 trigger sites don't change. Flag-gated (E2A_WEBHOOK_FANOUT_MODE, default legacy) + reconciler, 5 slices, deleting ~200 lines of LISTEN/reconnect/lease/poll code at the end. Not a durability fix (the current path is durable + correct) — a consistency/code-health cleanup, the lowest-priority of the River migrations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfAXBRMJ3KPuU3UEgwqjhS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scopes the last River migration: moving the
webhookpub.OutboxWorker(the hand-rolled loop that fanswebhook_events→webhook_subscriber_deliveries, Layer 1 → Layer 2) onto River, completing the webhook triad after the delivery migration.Design in one line
Same three-layer, in-tx-enqueue pattern as inbound (#391) and delivery: the trigger writes the
webhook_eventsfact row and enqueues awebhook_fan_outRiver job (event_id) in one tx; aFanOutWorkerre-reads, matches subscriptions, inserts deliveries + enqueues the existing delivery jobs, marks the event terminal. The enqueue seam is the twoOutboxmethods (PublishTx/PublishBestEffortTx) — both already hold the caller's tx — so the ~10 trigger call sites don't change.Why (and why it's low priority)
It's the sole remaining bespoke background loop doing durable work, hand-rolling what River already gives: leasing, lease-expiry reclaim, retry/backoff,
LISTEN-driven low-latency pickup, multi-replica safety. It is not a durability gap —webhook_eventsis durable + retry-forever and the fallback poll covers missed NOTIFYs — so this is a consistency/code-health cleanup that deletes ~200 lines, not a fix.Slices (one PR each)
fanout_job_idcolumn + reconciler partial index (CONCURRENTLY). Additive.internal/webhookfanoutworker + registrar + reconciler behindE2A_WEBHOOK_FANOUT_MODE(defaultlegacy), dormant.rivermode.OutboxWorker(LISTEN/reconnect, poll ticker, manual lease), thewebhook_events_newNOTIFY trigger, and the flag.Preserved
(event_id, webhook_id)dedup +WHERE status='pending'guard (kept verbatim → idempotent under at-least-once, safe across cutover),processed/no_matchstates, the delivery-history API, redelivery/replay (doesn't flow through fan-out — untouched), and sub-second latency (River pickup is itselfLISTEN/NOTIFY-driven).Open decisions (recommendations in doc §4.3, §8)
QueueWebhook(recommended) vs. a dedicatedQueueWebhookFanoutlane.🤖 Generated with Claude Code