At-Most-Once
At-Most-Once Delivery
At-most-once guarantees a message is delivered zero or one time - never more.
How it Works
At-Most-Once Flow:
1. Producer sends message
2. Message stored in queue
3. Consumer receives message
4. Consumer processes message
5. Consumer acknowledges (BEFORE processing)
If consumer crashes after ack:
- Message lost!
- Never reprocessed
- But never duplicated
Implementation
# Kafka auto-commit (at-most-once)
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "5000");
# Messages committed before processing
# Crash after commit = message lost
Trade-offs
| Advantage | Disadvantage |
|---|---|
| Fastest delivery | May lose messages |
| Simplest implementation | No retry guarantee |
| Lowest latency | Data loss risk |
When to Use
- Log collection (losing some logs OK)
- Metrics collection (approximate is fine)
- Non-critical events
- High throughput, low importance
Example
# At-most-once for metrics
def process_metric(metric):
# Ack first (message may be lost if crash)
queue.acknowledge(message_id)
# Then process
store_metric(metric)
At-Least-Once
At-Least-Once Delivery
At-least-once guarantees a message is delivered one or more times - never zero.
How it Works
At-Least-Once Flow:
1. Producer sends message
2. Consumer receives message
3. Consumer processes message
4. Consumer acknowledges (AFTER processing)
If consumer crashes before ack:
- Message redelivered
- May be processed twice
- But never lost
Implementation
# Kafka manual commit (at-least-once)
props.put("enable.auto.commit", "false");
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
processMessage(record.value()); // Process first
}
consumer.commitSync(); // Then commit
}
# Crash before commit = message redelivered
Deduplication Required
class AtLeastOnceConsumer:
def __init__(self, db):
self.db = db
def process(self, message):
# Check if already processed
if self.db.is_processed(message['id']):
return # Skip duplicate
# Process and mark as processed
with self.db.transaction():
self.handle(message)
self.db.mark_processed(message['id'])
Trade-offs
| Advantage | Disadvantage |
|---|---|
| No message loss | May process duplicates |
| Reliable delivery | Needs idempotency |
| Common pattern | Slightly more complex |
When to Use
- Order processing
- Payment processing
- Any critical business logic
- When duplicates can be handled
Exactly-Once
Exactly-Once Delivery
Exactly-once guarantees a message is delivered exactly one time - no more, no less.
How it Works
Exactly-Once Flow:
1. Producer sends message with unique ID
2. Broker deduplicates by ID
3. Consumer processes message
4. Consumer commits with transaction
5. Consumer marks as processed atomically
Combines:
- Idempotent producer (no duplicates from retries)
- Transactional consumer (atomic processing + commit)
Kafka Implementation
// Idempotent producer
props.put("enable.idempotence", "true");
props.put("acks", "all");
props.put("retries", Integer.MAX_VALUE);
// Transactional consumer
props.put("isolation.level", "read_committed");
// Transaction example
producer.initTransactions();
producer.beginTransaction();
try {
// Process message
processMessage(record.value());
// Produce output
producer.send(new ProducerRecord<>("output-topic", result));
// Commit offset atomically
producer.sendOffsetsToTransaction(offsets, consumerGroupId);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
Components
1. Idempotent Producer:
- Each message has unique sequence ID
- Broker deduplicates
- Prevents producer retries causing duplicates
2. Transactional Consumer:
- Read committed isolation level
- Only sees committed messages
- Atomic processing + offset commit
3. Consumer Offset Transactions:
- Process message + commit offset atomically
- Prevents: processed but not committed
Trade-offs
| Advantage | Disadvantage |
|---|---|
| No duplicates, no loss | Highest complexity |
| Strongest guarantee | Performance overhead |
| True exactly-once | Limited support |
When to Use
- Financial transactions
- Critical business operations
- When duplicates are unacceptable
- When at-least-once + idempotency isn't sufficient
Practice Problems
Design a scalable Delivery Semantics system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Delivery Semantics to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Delivery Semantics and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What does at-most-once guarantee?
2. Why does at-least-once need idempotency?
3. What makes exactly-once different from at-least-once?
4. Which delivery semantic is simplest?
5. When should you use exactly-once delivery?
Flashcards
Question
At-most-once guarantee?
Click to reveal answer
Answer
Message delivered 0 or 1 time - may lose messages but never duplicates. Ack before processing.
Question
At-least-once guarantee?
Click to reveal answer
Answer
Message delivered 1+ times - never lost but may duplicate. Ack after processing. Needs idempotency.
Question
Exactly-once guarantee?
Click to reveal answer
Answer
Message delivered exactly once. Uses idempotent producer + transactional consumer for atomic processing.
Question
Why idempotent consumer needed?
Click to reveal answer
Answer
At-least-once may redeliver messages; idempotent consumer handles duplicates safely
Question
When use each delivery semantic?
Click to reveal answer
Answer
At-most-once: logs/metrics. At-least-once: order processing. Exactly-once: financial transactions.
Revision Notes
Key Takeaways
- 1.At-most-once: simple, may lose messages
- 2.At-least-once: reliable, needs idempotency for duplicates
- 3.Exactly-once: strongest guarantee, highest complexity
- 4.Choose based on business requirements
- 5.At-least-once + idempotency is most common
Interview Tips
- •Know the three semantics and their trade-offs
- •Explain why at-least-once needs idempotency
- •Discuss exactly-once implementation with Kafka transactions
- •Give examples of when to use each semantic
Cheat Sheet
Cheat Sheet: Delivery Semantics
At-Most-Once
- 0 or 1 delivery
- Ack before processing
- May lose messages
- Use: Logs, metrics
At-Least-Once
- 1+ delivery
- Ack after processing
- May duplicate
- Needs idempotency
- Use: Order processing
Exactly-Once
- Exactly 1 delivery
- Idempotent producer
- Transactional consumer
- Atomic processing + commit
- Use: Financial
Trade-offs
Simple → Complex
At-most → At-least → Exactly