Reliability
AMQP 1.0 delivery guarantees on KubeMQ — settlement modes, delivery-state to Ack/NAck mapping, at-least-once vs at-most-once, and durable subscriptions.
AMQP 1.0 reliability is settlement modes + delivery state, not publisher confirms and not numeric reason codes. This guide is the practical playbook for getting the delivery guarantee you want from the KubeMQ AMQP 1.0 connector: which settlement modes exist (and which is rejected), how each delivery-state outcome maps to a KubeMQ queue action, how durable subscriptions resume, what happens on disconnect (nothing is lost), and why there is no connector dead-letter exchange.
For the credit machinery that governs when deliveries arrive — and the pre-settled-pattern data-loss footguns — see Flow control.
Settlement modes — pick your guarantee at ATTACH
Settlement is negotiated when the link attaches.
snd-settle-mode (the produce / out path)
| Requested | Server behavior | Guarantee |
|---|---|---|
settled | honored — each outbound TRANSFER carries settled=true and the server acks it immediately at send | at-most-once (pre-settled) |
unsettled / mixed / absent | server uses unsettled — sends deliveries unsettled, tracks them, waits for your DISPOSITION | at-least-once (default) |
- At-least-once (default): leave
snd-settle-modeunset (orunsettled). The server keeps each delivery tracked until you settle it; if you never do (you disconnect), it is requeued. - At-most-once: request
snd-settle-mode=settled. Use this for high-throughput fire-and-forget where occasional loss is acceptable. (Events are always pre-settled regardless — see Events.)
rcv-settle-mode (the consume / in path)
- The server always replies
rcv-settle-mode=first. rcv-settle-mode=secondis NOT implemented. Requesting it closes the link withDETACH(amqp:not-implemented)before the link is built. Pinfirst.
There is no two-stage (second) receiver settlement. Your consumer sends a DISPOSITION with a
terminal delivery state and that settles the delivery in one step.
Delivery-state outcomes → KubeMQ actions
When your client is the receiver (consuming a queue), it settles each delivery by sending a
DISPOSITION carrying a terminal delivery state. The connector maps that state to a KubeMQ
queue AckRange (settle/remove) or NAckRange (requeue):
| Client delivery state | KubeMQ action | Queue request | Effect on the message |
|---|---|---|---|
accepted | settle / consume | AckRange | removed from the queue |
rejected | discard | AckRange | discarded; poison handled by the broker MaxReceiveQueue policy — there is NO connector DLX |
released | requeue to tail | NAckRange | redelivered with a grown delivery-count, first-acquirer=false; increments receive-count toward MaxReceiveCount |
modified{delivery-failed} / modified{undeliverable-here} | requeue to tail | NAckRange | requeued (no per-consumer exclusion) |
| nil state (settled, no outcome) | treat as success | AckRange | removed |
| unknown terminal state | conservatively requeue | NAckRange | requeued — never silently dropped |
- A
DISPOSITIONmay cover afirst..lastdelivery-id range; the connector resolves it against the per-link unsettled map, groups byRefTransactionId, and emits oneAckRange/NAckRangeper group. Re-settling an already-settled id is idempotent. rejecteddoes NOT dead-letter through the connector. It discards (AckRanges) the message; whether a repeatedly-failing message is moved aside is a broker-sideMaxReceiveQueuepoison policy, not an AMQP-controllable per-link feature.
released / modified increment the receive-count. Every NAck-for-redelivery bumps
ReceiveCount, so a message you keep releasing will eventually hit the broker's MaxReceiveCount
cap and be removed even though you never rejected it. There is no requeue-without-increment.
Body sections
A message body must be one of two AMQP sections:
Data(binary) — the default; multipleDatasections concatenate.AmqpValue(a typed value) — use for typed bodies.
An empty body is valid (it becomes an empty Data body downstream).
AmqpSequence bodies are rejected. Only Data and AmqpValue are supported. A message
carrying an AmqpSequence body section gets a rejected DISPOSITION then a DETACH with
amqp:not-implemented. Emit Data by default and AmqpValue for typed bodies; never
AmqpSequence.
Confirming a produce
When your client is the sender, the server is the receiver and settles your delivery for you:
- On broker success it emits a settled
DISPOSITION(role=receiver, accepted). - On failure it emits
rejected{condition}: broker-not-ready →amqp:not-allowed; decode error →amqp:decode-error; translate error →amqp:invalid-field; array/broker error →amqp:internal-error. - A pre-settled inbound delivery gets no disposition; a failure is dropped, logged, and
counted in
kubemq_amqp10_transfers_in_dropped_total. If you need to know a produce succeeded, do not pre-settle — send unsettled and read the server'sDISPOSITION.
Durable subscriptions
The events-store pattern is a durable, replayable subscription. To consume durably, attach a
receiver from events-store/<ch> with terminus expiry-policy = never, a stable
container-id, and a stable link Name. On reconnect with the same identity, the
subscription resumes where it left off.
The durable identity is derived from your container-id and link name. The practical
consequence: to resume, reconnect with the same container-id AND the same link name. Change
either and you get a different durable subscription that starts fresh.
Durable subscriptions (and dynamic reply nodes) are node-local: a durable subscription created on node A is not visible from node B. Cluster-wide uniqueness of the durable identity is still enforced, but a durable subscriber must reconnect to the same node to resume — use load-balancer session affinity or a sticky connection.
A durable receiver's start position is set with the link property x-opt-kubemq-start (it
applies only to events-store):
x-opt-kubemq-start value | Meaning |
|---|---|
"" or new-only | only messages published after the subscription starts (the default) |
first | replay from the beginning of stored history |
last | start from the most recent stored message |
sequence:<n> | start at sequence number <n> (1-based, non-negative) |
time:<RFC3339|unix-seconds> | start at a wall-clock time |
time-delta:<seconds> | start <seconds> ago from now |
There is no "last N by count" — use sequence:, time:, or time-delta: to bound a replay.
A malformed value (sequence:abc, time:not-a-time, or an unknown token) is rejected at attach
with DETACH(amqp:invalid-field) naming the offending token.
Redelivery and teardown NAck-all
released, modified, and disconnect-with-unsettled all requeue the delivery to the tail. A
fresh consumer recovers it. A redelivered copy carries a grown header.delivery-count and
first-acquirer=false, so your consumer can detect a redelivery.
On link detach, connection close, or graceful shutdown, every unsettled delivery is
NAckRange'd exactly once — returned to the queue tail (the client experiences it as
released). A disconnecting at-least-once consumer loses nothing: whatever it had not yet
accepted is simply redelivered to the next consumer. This guarantee applies to queue consume
links — events and events-store are pre-settled and have their own footguns (see
Flow control).
No connector DLX, no visibility timeout
The connector has no dead-letter exchange and no visibility/redelivery timeout:
rejecteddiscards viaAckRange; it does not route to a dead-letter destination.- There is no per-link "make this message invisible for N seconds" verb. Queue receive is destructive, credit-based consume only — no peek, no browse.
- Poison handling is entirely broker-side: the broker's
MaxReceiveQueue/MaxReceiveCountpolicy removes a message redelivered too many times.
Design your consumer accordingly: accept on success, reject for a permanently-bad message
(discarded, with the broker policy as your only poison backstop), and release / modify for a
transient failure you want retried — knowing each retry bumps the receive-count toward the
broker's cap.
Decision guide
| You want… | Do this |
|---|---|
| At-least-once consume (no loss on disconnect) | Consume unsettled (default rcv-settle-mode=first); accept on success; rely on teardown NAck-all |
| At-most-once produce (fire-and-forget) | Request snd-settle-mode=settled |
| Confirm a produce succeeded | Send unsettled and read the server's DISPOSITION(accepted/rejected) |
| Retry a transient failure | release (or modify) — but each retry increments the receive-count |
| Discard a permanently-bad message | reject — discarded via AckRange; broker MaxReceiveQueue is the poison backstop |
| Resume after reconnect | events-store + stable container-id + stable link name + expiry-policy=never |
Related
Flow control
Link credit, drain, prefetch, and the two silent data-loss footguns on the pre-settled patterns.
Queues
Competing-consumer work queues — the at-least-once pattern these settlement rules govern.
Error conditions
The amqp:* conditions a denied, malformed, or unsupported request closes a link with.
Was this page helpful?
Flow Control
AMQP 1.0 link credit on KubeMQ — who grants credit on produce vs consume links, prefetch and MaxUnsettledPerLink, drain, and pre-settled data-loss footguns.
TLS and mTLS
Securing the AMQP 1.0 connector — amqps on port 5671, server-auth TLS vs mutual TLS, and SASL EXTERNAL with the certificate CN as ClientID.