Scenario: Reliable Delivery
Objective
Test Danube's reliable dispatch mechanics — at-least-once delivery guarantees, NACK handling, ack timeouts, failure policies, and consumer reconnection behavior. These are the features that ensure no message is lost in production.
When to Use
- User wants to test "reliable", "at-least-once", "NACK", "redelivery", "dead letter"
- User wants to verify message redelivery after consumer failure
- User wants to test failure policies (block, drop, dead-letter)
- User wants to verify consumer reconnection behavior
Compatible Infrastructure
| Setup Method | Standalone | Cluster | Notes |
|---|---|---|---|
| Local Binary | ✅ | ✅ | danube-admin needed for topic creation and failure policy setup |
| Local Source | ✅ | ✅ | Same |
| Docker Compose | ✅ | ✅ | Use exposed ports |
| Kubernetes | — | ✅ | Use port-forwarded addresses |
All tests work on standalone. Reconnection failover is more meaningful with Shared subscriptions (multiple consumers).
AI Decision Flow
1. Which reliable delivery aspect to test?
Present these options to the user exactly as listed:
Basic Reliable: Produce and consume messages with reliable dispatch (at-least-once). Verify all messages are received, each is explicitly acknowledged, and the producer receives message IDs as confirmation.
Redelivery (NACK & Timeout): Test both redelivery triggers — consumer explicitly NACKs a message (immediate redelivery) and consumer fails to ack within the timeout (broker-initiated redelivery). Verify the same message is redelivered with the same offset in both cases.
Failure Policies: Test what happens when a message exhausts its retry budget — block (subscription stalls), drop (message skipped, next delivered), or dead-letter (message routed to a DLQ topic with origin metadata). Covers all three poison message strategies.
Consumer Reconnection: Two consumers share a Shared subscription. When one consumer disconnects, verify the broker resends its pending (unacked) messages to the surviving consumer. Tests failover behavior.
Each aspect maps to the corresponding Step 2x in Execution Steps below.
2. Tool or Client Language?
| User says | Choice |
|---|---|
| "python", "rust", "go", "java" | Client library — read clients/<lang>/SKILL.md |
| (unclear) | Default: Python |
Note: All reliable delivery tests require a client library. The CLI cannot NACK messages or control ack timing. Auto-install the library.
3. Subscription type?
| User says | Type |
|---|---|
| "exclusive", "single" | Exclusive — single consumer, ordered redelivery |
| "shared", "queue", "multiple" | Shared — multiple consumers, failover on disconnect |
| (unclear) | Default: Exclusive for NACK/timeout/policy tests, Shared for reconnection tests |
Execution Steps
Step 1: Create a Reliable Topic
All reliable delivery tests require a topic created with --dispatch-strategy reliable:
danube-admin topics create /default/reliable-test --dispatch-strategy reliable
Step 2a: Basic Reliable (if selected)
- Create a reliable producer (
.with_reliable_dispatch()/.with_dispatch_strategy(RELIABLE)) - Create a consumer and subscribe
- Send N messages
- Consumer receives and acks each message
- Verify: all messages received, producer got message IDs (acknowledgments)
Step 2b: Redelivery — NACK & Timeout (if selected)
Before running: Set a failure policy to allow retries:
danube-admin topics set-failure-policy /default/reliable-test \
--subscription test-sub \
--max-redelivery-count 2 \
--ack-timeout-ms 5000 \
--base-redelivery-delay-ms 50 \
--max-redelivery-delay-ms 50 \
--backoff-strategy fixed \
--poison-policy block
Part 1: NACK Redelivery
- Create reliable producer + consumer
- Send 1 message
- Consumer receives the message but calls
consumer.nack(message, delay_ms=0, reason="retry") - Consumer receives the SAME message again (redelivery)
- Consumer acks the redelivered message
- Verify: same payload and same
topic_offseton redelivery
Part 2: Ack Timeout Redelivery
- Update failure policy with short ack timeout:
danube-admin topics set-failure-policy /default/reliable-test \
--subscription test-sub \
--max-redelivery-count 2 \
--ack-timeout-ms 200 \
--base-redelivery-delay-ms 50 \
--max-redelivery-delay-ms 50 \
--backoff-strategy fixed \
--poison-policy block
- Send 1 message
- Consumer receives the message but does NOT ack it
- Wait for timeout — broker redelivers the same message
- Consumer acks the redelivered message
- Verify: same payload and same
topic_offset
Step 2c: Failure Policies (if selected)
Three sub-flows depending on which policy the user wants:
Block Policy
Stops subscription progress when a message exhausts its retry budget:
- Create reliable topic + set failure policy with
--max-redelivery-count 0 --poison-policy block - Send message A, consumer NACKs it
- Send message B
- Verify: message B is NOT delivered (subscription is stalled)
Drop Policy
Skips the poisoned message and continues:
- Create reliable topic + set failure policy with
--max-redelivery-count 0 --poison-policy drop - Send message A, consumer NACKs it
- Send message B
- Verify: message B IS delivered (poisoned message was skipped)
Dead Letter Queue
Routes poisoned messages to a separate DLQ topic:
- Create main topic + DLQ topic (both reliable)
- Set failure policy with
--poison-policy dead_letter --dead-letter-topic /default/reliable-test-dlq - Send message, consumer NACKs it
- Verify: message appears on DLQ with these metadata attributes:
x-original-topic— source topic name (e.g.,/default/reliable-test)x-original-subscription— subscription that NACKed the messagex-poison-policy—dead_letterx-failure-reason— the reason string passed in the NACK callx-original-broker-addr— broker address that handled the messagex-original-producer-id— ID of the producer that sent the messagex-original-topic-offset— offset of the message on the original topicx-delivery-attempt— number of delivery attempts before DLQ routing
- Verify: main subscription continues with next message
# Create DLQ topic
danube-admin topics create /default/reliable-test-dlq --dispatch-strategy reliable
# Set dead-letter policy
danube-admin topics set-failure-policy /default/reliable-test \
--subscription test-sub \
--max-redelivery-count 0 \
--ack-timeout-ms 5000 \
--base-redelivery-delay-ms 50 \
--max-redelivery-delay-ms 50 \
--backoff-strategy fixed \
--poison-policy dead_letter \
--dead-letter-topic /default/reliable-test-dlq
Step 2d: Consumer Reconnection (if selected)
- Create reliable producer + 2 Shared consumers on same subscription
- Send message, consumer A receives it
- Disconnect consumer A (drop the consumer object)
- Verify: broker resends the pending message to consumer B
- Consumer B acks it
Verification
| Test | Pass Criteria |
|---|---|
| Basic Reliable | All messages received and acked, producer got IDs |
| NACK Redelivery | Same message redelivered after NACK (same offset) |
| Ack Timeout | Same message redelivered after timeout (same offset) |
| Block Policy | Subscription stalls — no new messages after poison |
| Drop Policy | Next message delivered — poisoned message skipped |
| Dead Letter | Message on DLQ with origin metadata, main subscription continues |
| Reconnection | Pending message resent to surviving consumer |
# Inspect topic state
danube-admin topics describe /default/reliable-test
# Check failure policy
danube-admin topics get-failure-policy /default/reliable-test \
--subscription test-sub --output json
Cleanup
This scenario only cleans up topics it created. See setups/SKILL.md → Cleanup for cluster teardown.
danube-admin topics delete /default/reliable-test
danube-admin topics delete /default/reliable-test-dlq # if DLQ was created