feat(indexer): dead-letter queue for events that repeatedly fail to persist - #549
feat(indexer): dead-letter queue for events that repeatedly fail to persist#549Salmatcre8 wants to merge 1 commit into
Conversation
|
@Salmatcre8 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
…ted metrics, backlog observability Rebuilt against dev after Telocel-Labs#208 landed the queue itself (failed_events, per-event isolation, cursor advance past captured events) while this PR was in flight — the remaining Telocel-Labs#508 criteria, delivered as a small delta on that foundation: - Pending rows are unique per event (migration 0030, keyed by the same contract/ledger/index triple 0025 made canonical): a poison event re-encountered across polls updates one row with attempts folded in and the latest error kept, so the queue's pending count means distinct poisoned events. Existing duplicates are collapsed with their attempt counts preserved; replayed rows are history and never block recording a fresh failure of the same event. - The persist path counts on its own series instead of the parse-DLQ counter it shared: trident_indexer_persist_dead_lettered_total, with both counters now described accurately. - A silent DLQ is the same as data loss, so the pending depth is published as trident_indexer_persist_dead_letter_backlog (refreshed each active poll cycle and on every dead-letter write), alerted on by TridentIndexerPersistDeadLetterBacklog for as long as any pending row exists, with a runbook covering diagnosis, backfill replay and its scope limits, and the replayed_at bookkeeping that resolves the alert. promtool-validated; metrics catalog updated. - An env-gated test proves redelivery collapses to one row with folded attempts, and that a replayed row does not block a fresh failure. Closes Telocel-Labs#508
de3cf7b to
3f493ce
Compare
|
Rebuilt rather than rebased: #208 landed the dead-letter queue itself on dev (
Verified against a live Postgres with the migration applied; promtool-clean; fmt/clippy clean. (Two pre-existing |
Closes #508 (related to #208)
Problem
A persistently failing event blocked the pipeline forever: commit failures are
StorageError → Retryable, so the streamer refetched and re-failed the identical page every poll with no budget and no capture path. Parse failures have had a DLQ since #414; persistence failures had nothing — e.g. an event whose malformed ledger timestamp fails column conversion deterministically.What this does
PERSIST_FAILURE_BUDGETconsecutive polls triggers isolation: events commit one at a time (each with its token projection, and each with its own bounded retry so a transient DB blip during isolation cannot mass-dead-letter healthy events); events that still fail are captured in the newdead_letter_eventstable (migration 0027) with full payload, reason, and attempt count, deduplicated on the natural key.ledgerClosedAtpoisoning the page's last event AND the LedgerMeta row built from it) advances via a cursor-only fallback that drops the poisoned ledger row rather than wedging — regression-tested.trident_indexer_persist_dead_letter_backlog(published at the top of every poll so failing cycles still refresh it) drives the new TridentIndexerPersistDeadLetterBacklog alert, which fires for as long as any row exists; rows are removed only by the documented replay procedure.trident_indexer_persist_dead_lettered_totalcounts captures only after a durable insert. Both metrics described/seeded;promtoolclean.soroban_eventsrows only — no outbox delivery or token projections), verify, then clear replayed rows via a natural-key join DELETE./admin/dead-letterendpoint (selected a non-existent column and scanned a UUID into an i64 — it 500'd against the real schema) and extends it to list both queues.Done-when check (integration-tested against real Postgres + Redis + mock RPC)
A poison event lands in the DLQ with its reason and round-trippable payload → ingestion continues (good events in the same page indexed, cursor durably advanced) → the backlog alert has a live series → replay is documented. Budget-reset and tail-poison edge cases have dedicated tests. Full workspace suite green.