Schedule icon
Webhook icon
Assert icon
Request icon
Script icon
Set icon
If icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Sleep icon
Fail icon
Log icon

Audit and Replay Stalled KurrentDB Persistent Subscription Parked Messages

Audit KurrentDB persistent subscription parked messages, track backlog trends in KV, and replay with operator approval and verification in Kestra.

Categories
DataInfrastructure

KurrentDB (formerly EventStoreDB) persistent subscriptions deliver events to competing consumers with explicit acknowledgment. When a consumer repeatedly NACKs or fails to process an event, the subscription's retry policy eventually "parks" that event: it is pulled out of the live delivery stream into a dedicated parked-messages stream so a single bad event cannot block the rest of the subscription.

This isolation protects throughput, but parked messages are easy to lose track of. Nothing in KurrentDB automatically retries them, and unlike a subscription's live lag, a growing parked count produces no read-side errors downstream, just quietly missing business events (an order confirmation that never fires a side effect, a billing event that is never applied) until someone happens to check parkedMessageCount by hand.

This blueprint provides an automated guard that lists every persistent subscription on the cluster, fetches each one's parked-message count via the HTTP Admin API, correlates it against historical snapshots in Kestra KV to separate a transient blip from a subscription that is genuinely stuck, requests human approval, calls replayParked to re-inject the backlog into live processing, and re-queries the subscription to verify the parked count actually decreased.

How it works

  1. Pre-flight Assertion: validate_inputs (io.kestra.plugin.core.execution.Assert) checks that critical_parked_threshold >= parked_message_threshold and that all thresholds are positive.
  2. Subscription Discovery: list_subscriptions (io.kestra.plugin.core.http.Request) calls GET /subscriptions on the KurrentDB HTTP Admin API to enumerate registered persistent subscriptions. This endpoint does not expose parkedMessageCount, only identity and coarse status.
  3. Per-Subscription Triage: triage_subscriptions (io.kestra.plugin.scripts.python.Script) calls GET /subscriptions/{stream}/{group}/info for each discovered subscription to read its real parkedMessageCount, compares it against the prior snapshot in Kestra KV (inputs.cursor_key), and classifies a subscription as stalled when its backlog is at or above critical_parked_threshold (immediate escalation) or has persisted or grown for min_consecutive_runs consecutive audits.
  4. State Persistence: save_tracker_kv (io.kestra.plugin.core.kv.Set) writes the updated per-subscription trend snapshot back to Kestra KV.
  5. Slack Triage Alert: When stalled subscriptions exist, dispatch_slack_alert posts affected subscriptions, parked counts, and escalation reasons.
  6. Human Approval Gate: When auto_remediate is enabled and dry_run is false, pause_for_operator_approval (io.kestra.plugin.core.flow.Pause) halts execution until an operator resumes with approved: true.
  7. Replay and Verify: remediate_stalled_subscriptions (io.kestra.plugin.core.flow.Loop) calls POST /subscriptions/{stream}/{group}/replayParked per approved subscription, waits briefly for redelivery, re-queries /info, and fails loudly via io.kestra.plugin.core.execution.Fail if the parked count did not actually decrease.
  8. Resolution Notification: A confirmation card is posted to Slack once remediation completes, and the full audit manifest is archived in Kestra KV.
  9. Failure Handling: A flow-level errors block alerts Slack if cluster connectivity, authentication, or scripting fails unexpectedly.

What you get

  • Automated discovery of parked-message backlogs across every persistent subscription on a KurrentDB cluster, not just one hardcoded stream.
  • Historical trend tracking in Kestra KV to distinguish a one-off parked event from a genuinely stuck consumer.
  • Human-in-the-loop approval before any replayParked call touches live subscription state.
  • Post-remediation verification that fails loudly instead of assuming the replay worked.
  • Full audit trail of every triage and remediation pass stored in Kestra KV.

Who it's for

  • Platform Engineers and SREs operating KurrentDB (EventStoreDB) clusters for event-sourced or CQRS systems.
  • Backend Engineers who own persistent-subscription consumers and need visibility into silently dropped events.
  • Incident responders recovering from a bad deploy that caused a consumer to NACK a batch of events into the parked stream.

Why orchestrate this with Kestra

KurrentDB's HTTP Admin API exposes parkedMessageCount and replayParked, but nothing in the database itself polls for backlog, tracks trend across checks, or requires sign-off before replaying. A cron job calling replayParked blindly risks replaying a backlog caused by a genuine, still-broken consumer in an infinite park-replay-park loop. Kestra adds declarative scheduling, KV-backed trend state, an interactive approval gate, and verified, auditable remediation on top of the raw API.

Prerequisites

  • A reachable KurrentDB (EventStoreDB) cluster, version 20.6 or higher, with the external HTTP port (default 2113) reachable from the Kestra worker.
  • An admin or operations-scoped user able to read subscription statistics and call replayParked (the default admin user has this by default).
  • A Slack Incoming Webhook URL for triage and resolution notifications.

Secrets

  • KURRENTDB_USERNAME: KurrentDB user with permission to read subscription stats and call replayParked.
  • KURRENTDB_PASSWORD: Password for that user.
  • SLACK_WEBHOOK_URL: Slack Incoming Webhook endpoint for triage and resolution alerts.
  • KURRENTDB_GUARD_WEBHOOK_KEY: Secret authentication token protecting the on-demand webhook trigger.

Quick start

  1. Configure KURRENTDB_USERNAME, KURRENTDB_PASSWORD, SLACK_WEBHOOK_URL, and KURRENTDB_GUARD_WEBHOOK_KEY in your Kestra namespace secrets.
  2. Import this blueprint and set kurrentdb_url to your cluster's external HTTP endpoint.
  3. Run a manual execution with the default dry_run: true to audit current parked backlogs without mutating any subscription.
  4. Review the execution logs, Slack triage card, and the KV entry under inputs.cursor_key.
  5. Run with auto_remediate: true and dry_run: false to arm remediation; when stalled subscriptions are detected, approve the pause_for_operator_approval task with approved: true and observe the verified replay.
  6. Enable the periodic_parked_message_audit schedule trigger for continuous monitoring.

How to extend

  • Per-Subscription Thresholds: Replace the single parked_message_threshold with a KV-backed per-subscription override for high-value streams that should alert on a single parked event.
  • Permanent Discard Path: Add a branch calling POST /subscriptions/{stream}/{group}/truncateParked for subscriptions whose parked messages are confirmed unrecoverable (bad schema, deleted aggregate) rather than replayable.
  • PagerDuty Escalation: Add io.kestra.plugin.pagerduty.PagerDutyAlert when overall_status is CRITICAL to page the on-call engineer directly.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.