Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sink dead-letter queue (DLQ)

Sinks transparently retry transient failures with exponential backoff + jitter. Anything that survives the retry budget — or is classified as terminal from the first call — lands in the DLQ. An operator inspects, replays, or discards items from there.

Backends

BackendWhen it activatesPersistence
memoryOptions.RedisClient == nilProcess-local; lost on restart
redisOptions.RedisClient != nil (or --redis-addr)Redis Streams, one stream per sink (parsec:dlq:<sink>)

The backend in use is reported in the dlq_backend field of parsec --json.

Retry policy

Default config (overridable via Options.SinkRetry or Options.PerSinkRetry):

FieldDefault
MaxAttempts5
BaseBackoff1s
MaxBackoff30s
JitterFraction0.2

Backoff per attempt is BaseBackoff * 2^(attempt-2) capped at MaxBackoff, multiplied by uniform(1 - JitterFraction, 1 + JitterFraction) to break thundering herds.

Set MaxAttempts = 1 to disable retries (terminal-or-DLQ on first error).

CLI

parsec dlq list <sink>            # newest first
parsec dlq count <sink>           # XLEN
parsec dlq discard <id>           # XDEL
parsec dlq replay <id>            # re-run through the original sink

Every command honors --server and --token (or PARSEC_SERVER / PARSEC_TOKEN).

Inspecting items

$ parsec dlq list email --limit 10
{
  "format_version": "1.0",
  "kind": "parsec.dlq.list",
  "payload": {
    "sink": "email",
    "items": [
      {
        "id": "1741310221012-0",
        "sink": "email",
        "at": "2026-05-22T18:30:21Z",
        "subject": "Order shipped",
        "body": "Your package is on the way.",
        "attempts": 5,
        "last_error": "smtp send: 451 4.4.5 Temporary error",
        "recipient": {"address": "alice@example.com"}
      }
    ]
  }
}

Replaying

Replay reruns the stored item through the original sink. If the sink is wrapped by a Retrier (the default), the replay itself retries up to MaxAttempts. If it fails again it lands back in the DLQ as a new item — the original entry stays in place so you can compare attempt counts.

$ parsec dlq replay 1741310221012-0
{ "kind": "parsec.dlq.replayed", "payload": {"id": "1741310221012-0"} }

Discarding

discard is final. Use it when the payload is irrelevant or the failure was diagnosed and the item is unrecoverable.

$ parsec dlq discard 1741310221012-0

RPC

MethodRequestResponse
DlqList{sink, limit}{items[]}
DlqCount{sink}{count}
DlqDiscard{id}Empty
DlqReplay{id}Empty

All methods require a mgmt bearer.

Stream growth & retention

The Redis backend uses XADD MAXLEN ~ 10000 by default — old entries are trimmed approximately when the stream grows past the cap. Override via RedisDLQ.WithMaxLen(n) for tighter or looser retention.

Error taxonomy

CodeMeaning
PARSEC_SINK_UNAVAILABLETransient sink failure (retry will run)
PARSEC_SINK_DLQ_OVERFLOWRetries exhausted and DLQ push itself failed
PARSEC_SINK_DLQ_NOT_FOUNDReplay/Discard referenced an unknown id
PARSEC_SINK_CONFIG_INVALIDSink misconfigured at registration time

PublishOrSink returns err == nil when the DLQ accepted the failed payload — the failure has been recorded. It returns an error only when the DLQ itself failed (overflow / Redis unreachable).