Chapter 24: Message Queues
RabbitMQ, SQS, and Delivery Guarantees
Why Message Queues
Message queues enable asynchronous communication between services, decoupling producers from consumers and providing buffering, load leveling, and resilience.
Key Benefits
| Benefit | Description | Example |
|---|---|---|
| Decoupling | Producers and consumers run independently | Order service doesn't wait for email service |
| Async Processing | Long operations don't block user requests | Image resize, video transcode, report generation |
| Load Leveling | Absorb traffic spikes, process at steady rate | Black Friday orders queued for gradual processing |
| Buffering | Store messages when consumers unavailable | Database down for maintenance, messages queue up |
| Fault Tolerance | Messages persist if consumer crashes | Payment processor restart doesn't lose payments |
| Scalability | Scale producers and consumers independently | Add 5 more worker instances during peak load |
Before/After Architecture
Answer: Decoupling (services fail independently), async processing (don't block user), load leveling (absorb spikes), and reliability (messages persist). Example: Order confirmation should succeed even if email service is down.
Core Concepts
| Concept | Definition | Notes |
|---|---|---|
| Producer | Service that sends messages to queue | Also called publisher or sender |
| Consumer | Service that receives and processes messages | Also called subscriber or worker |
| Queue | FIFO buffer storing messages | Messages consumed once then deleted |
| Topic | Named channel for pub/sub patterns | Messages delivered to all subscribers |
| Exchange | Routes messages to queues (RabbitMQ concept) | Direct, topic, fanout, headers routing |
| Binding | Rule connecting exchange to queue | Defines routing logic with binding keys |
| Acknowledgment (ACK) | Consumer confirms successful processing | Message deleted only after ACK |
| NACK | Negative acknowledgment - processing failed | Message requeued or sent to DLQ |
| Visibility Timeout | Time message hidden after delivery | Prevents duplicate processing (SQS concept) |
| Prefetch | Max messages delivered before ACK required | Controls consumer flow rate |
Basic Flow
Answer: Message not deleted, becomes visible again after timeout, redelivered to another consumer. This is at-least-once delivery - idempotency required.
Delivery Guarantees
| Guarantee | Description | Implementation | Trade-offs | Use Cases |
|---|---|---|---|---|
| At-Most-Once | Message delivered 0 or 1 time, may be lost | Auto-ACK immediately on receive, no retries | Fast, no duplicates, data loss possible | Metrics, logs, non-critical events |
| At-Least-Once | Message delivered 1+ times, duplicates possible | Manual ACK after processing, retry on failure | No data loss, duplicates require idempotency | Order processing, emails, notifications |
| Exactly-Once | Message delivered and processed exactly once | Deduplication + idempotent processing + transactions | Complex, slower, expensive, hard to achieve | Financial transactions, billing, inventory |
At-Most-Once: Auto ACK
// Consumer auto-ACKs on receive
channel.basicConsume(queueName, autoAck: true, consumer);
// If processing fails, message lost
processMessage(message); // crash here = lost message
At-Least-Once: Manual ACK
// Consumer ACKs after successful processing
channel.basicConsume(queueName, autoAck: false, consumer);
try {
processMessage(message);
channel.basicAck(deliveryTag); // ACK only on success
} catch (Exception e) {
channel.basicNack(deliveryTag, requeue: true); // retry
}
Exactly-Once: Deduplication + Idempotency
// Deduplication via message ID
String messageId = message.getMessageId();
if (processedIds.contains(messageId)) {
channel.basicAck(deliveryTag); // already processed
return;
}
// Idempotent processing with database transaction
db.transaction(() -> {
processMessage(message);
processedIds.add(messageId); // persist dedup ID
});
channel.basicAck(deliveryTag);
Comparison
| Aspect | At-Most-Once | At-Least-Once | Exactly-Once |
|---|---|---|---|
| Duplicates | No | Yes | No |
| Data Loss | Possible | No | No |
| Complexity | Low | Medium | High |
| Performance | Fastest | Fast | Slower |
| Cost | Lowest | Low | Higher |
| Idempotency Required | No | Yes | Yes |
| Deduplication Storage | No | No | Yes |
Answer: Store processed message IDs in database, check before processing. Use database transactions to atomically process + store ID. Make processing idempotent (e.g., SET balance = 100, not balance += 50). SQS FIFO has built-in deduplication (5-min window).
RabbitMQ
RabbitMQ implements AMQP (Advanced Message Queuing Protocol), featuring flexible routing via exchanges and strong delivery guarantees.
Architecture
Exchange Types
| Exchange Type | Routing Logic | Use Case | Example |
|---|---|---|---|
| Direct | Exact routing key match | Point-to-point, task distribution | Routing key "email" -> email queue |
| Topic | Pattern matching with wildcards (* #) | Selective pub/sub, hierarchical routing | "logs.error.*" matches "logs.error.db" |
| Fanout | Broadcast to all bound queues | Pub/sub, event broadcasting | User signup -> email, analytics, CRM |
| Headers | Match on message headers | Complex routing logic | Route by region, priority, type headers |
Topic Exchange Examples
// Wildcards: * matches one word, # matches zero or more words
Routing Key Binding Pattern Match?
logs.error.api logs.error.* Yes
logs.error.db.slow logs.error.* No
logs.error.db.slow logs.error.# Yes
logs.info.api logs.*.api Yes
logs.error.api.auth logs.# Yes
Core Features
| Feature | Description | Configuration |
|---|---|---|
| Durability | Queues and messages survive broker restart | durable: true, persistent: true |
| Prefetch | Max unACKed messages per consumer | channel.basicQos(prefetchCount: 10) |
| TTL | Message expires after timeout | x-message-ttl: 60000 (60 sec) |
| Priority | Higher priority messages delivered first | x-max-priority: 10 on queue |
| Dead Letter Exchange | Failed messages routed to DLX | x-dead-letter-exchange: dlx |
Queue Types (High Availability)
| Queue Type | Replication | Performance | Availability | When to Use |
|---|---|---|---|---|
| Classic | None (single node) | Fastest | Low | Development, non-critical |
| Mirrored | Active-passive replication | Slower | High | Legacy HA (deprecated) |
| Quorum | Raft consensus (majority ACK) | Moderate | Very High | Production HA (recommended) |
| Stream | Replicated log (Kafka-like) | High throughput | High | Event sourcing, large backlogs |
Answer: Quorum queues use Raft consensus - replicate to 3+ nodes, require majority ACK for writes. If leader fails, cluster elects new leader from followers. Data never lost unless majority of nodes fail simultaneously.
AWS SQS
Simple Queue Service: fully managed, serverless, highly scalable. Two queue types: Standard (best-effort ordering, unlimited throughput) and FIFO (strict ordering, 3000 msg/sec).
Standard vs FIFO Queues
| Feature | Standard Queue | FIFO Queue |
|---|---|---|
| Ordering | Best-effort ordering | Strict FIFO per message group |
| Delivery | At-least-once (duplicates possible) | Exactly-once processing (built-in dedup) |
| Throughput | Unlimited (nearly infinite) | 3000 msg/sec (batched: 30k/sec) |
| Deduplication | No | 5-minute deduplication window |
| Message Groups | No | Yes (ordered within group) |
| Use Case | High throughput, order not critical | Strict ordering required (e.g., banking) |
| Naming | Any name | Must end with .fifo |
Key Concepts
| Concept | Description | Default | Max |
|---|---|---|---|
| Visibility Timeout | Time message invisible after delivery | 30 seconds | 12 hours |
| Message Retention | How long messages stored if not consumed | 4 days | 14 days |
| Delivery Delay | Delay before message becomes visible | 0 seconds | 15 minutes |
| Receive Wait Time | Long polling wait time | 0 (short polling) | 20 seconds |
| Max Message Size | Maximum message payload | - | 256 KB |
| Max Receives | Delivery attempts before DLQ | ā | 1000 |
Visibility Timeout Flow
Long Polling vs Short Polling
| Aspect | Short Polling (default) | Long Polling (recommended) |
|---|---|---|
| Behavior | Returns immediately (even if empty) | Waits up to 20s for message |
| Empty Responses | Many (continuous polling) | Few (waits for messages) |
| Cost | Higher (more API calls) | Lower (fewer API calls) |
| Latency | Lower (if polling fast) | Similar (near real-time) |
| Configuration | WaitTimeSeconds: 0 |
WaitTimeSeconds: 20 |
Dead Letter Queue (DLQ)
// Configure DLQ on source queue
{
"RedrivePolicy": {
"deadLetterTargetArn": "arn:aws:sqs:us-east-1:123:my-dlq",
"maxReceiveCount": 3 // move to DLQ after 3 delivery attempts
}
}
Answer: Standard for high throughput where order doesn't matter (e.g., image processing, log ingestion). FIFO when strict ordering required (bank transactions, order fulfillment) - worth 100x throughput trade-off. FIFO also gives exactly-once processing via built-in deduplication.
Message Ordering
Maintaining message order at scale is challenging. Most systems provide ordering guarantees only within partitions or message groups.
Why Ordering is Hard
| Challenge | Description | Impact |
|---|---|---|
| Parallel Processing | Multiple consumers process messages concurrently | Messages complete out of order |
| Network Delays | Variable latency in distributed systems | Later messages arrive first |
| Retries | Failed messages reprocessed later | Older messages processed after newer ones |
| Replication | Multi-node replication has lag | Different nodes see different order |
| Partitioning | Messages split across partitions | Only ordered within partition, not globally |
Ordering Strategies
| Strategy | Scope | Implementation | Trade-off |
|---|---|---|---|
| No Ordering | None | Standard SQS, RabbitMQ with multiple consumers | Max throughput, no order guarantee |
| Single Consumer | Global | One consumer, prefetch=1 | Strict ordering, bottleneck, no parallelism |
| Partition Ordering | Per partition | Kafka: partition by key, one consumer per partition | Scalable, ordered within partition only |
| Message Groups | Per group | SQS FIFO: message group ID | Scalable, ordered within group only |
| Sequence Numbers | Application level | Add sequence number, reorder on consumer | Flexible, complex consumer logic |
SQS FIFO Message Groups
// Order maintained per user, but users processed in parallel
sqs.sendMessage({
QueueUrl: 'https://sqs.us-east-1.amazonaws.com/123/orders.fifo',
MessageBody: JSON.stringify(order),
MessageGroupId: userId, // all messages for user ordered
MessageDeduplicationId: orderId
});
Kafka Partition-Level Ordering
Application-Level Sequence Numbers
// Producer adds sequence number
{
"userId": 123,
"action": "update_profile",
"sequenceNumber": 42,
"data": {...}
}
// Consumer reorders out-of-order messages
Map<UserId, PriorityQueue<Message>> buffers;
long expectedSeq = getNextExpected(userId);
if (message.seq == expectedSeq) {
process(message);
expectedSeq++;
// process buffered messages now in order
while (!buffers[userId].isEmpty() &&
buffers[userId].peek().seq == expectedSeq) {
process(buffers[userId].poll());
expectedSeq++;
}
} else {
buffers[userId].add(message); // buffer for later
}
Answer: Use message groups (SQS FIFO) or partition by user ID (Kafka). Messages for same user always go to same partition/group, processed in order by single consumer. Different users processed in parallel across partitions. Don't need global ordering - just per-user ordering.
Dead Letter Queues
Dead Letter Queues (DLQs) isolate poison messages that repeatedly fail processing, preventing them from blocking the main queue.
DLQ Workflow
Common Causes of DLQ Messages
| Cause | Example | Resolution |
|---|---|---|
| Malformed Message | Invalid JSON, missing required field | Fix producer validation, update schema |
| Business Logic Error | User doesn't exist, insufficient funds | Handle edge case, alert business team |
| External Dependency | Third-party API permanently fails | Circuit breaker, fallback logic |
| Code Bug | Null pointer, division by zero | Fix bug, redeploy, reprocess DLQ |
| Resource Limit | File too large, timeout too short | Increase limits, split into smaller tasks |
Retry Strategies
| Strategy | Description | Use Case | Example Config |
|---|---|---|---|
| Immediate Retry | Retry instantly after failure | Transient network blips | 3 retries, no delay |
| Fixed Delay | Wait fixed time between retries | Rate limit recovery | 3 retries, 5 sec delay |
| Exponential Backoff | Double delay each retry | Overloaded services | 1s, 2s, 4s, 8s delays |
| Jittered Backoff | Exponential + random jitter | Prevent thundering herd | 1s±200ms, 2s±400ms, ... |
DLQ Monitoring
| Metric | Alert Threshold | Action |
|---|---|---|
| DLQ Depth | > 10 messages | Investigate root cause immediately |
| DLQ Growth Rate | > 5 msg/min | Active incident, page on-call |
| DLQ Age | Oldest message > 1 day | Stale DLQ, needs cleanup |
| Message Attributes | Group by error type | Identify patterns (e.g., all user 123) |
Reprocessing DLQ Messages
// 1. Fix the bug/issue causing failures
// 2. Move messages from DLQ back to main queue
// SQS: Redrive to source queue (AWS Console or API)
aws sqs start-message-move-task \
--source-arn arn:aws:sqs:us-east-1:123:my-dlq \
--destination-arn arn:aws:sqs:us-east-1:123:my-queue
// RabbitMQ: Shovel plugin or manual script
while (message = dlqChannel.basicGet("my-dlq")) {
mainChannel.basicPublish("", "my-queue", message.properties, message.body);
dlqChannel.basicAck(message.deliveryTag);
}
Answer: First, stop the bleeding - identify root cause (check error logs, message attributes). Fix issue (deploy code fix, scale dependency, update config). Then reprocess: sample DLQ messages to verify fix works, automate reprocessing with rate limiting to avoid overwhelming system, monitor success rate, keep DLQ as backup until verified.
Backpressure
Backpressure occurs when consumers can't keep up with producer rate. Without handling, queues grow unbounded, causing memory issues and increased latency.
Symptoms
| Symptom | Indicator | Impact |
|---|---|---|
| Growing Queue Depth | ApproximateNumberOfMessages increasing | Rising end-to-end latency |
| Consumer Lag | Kafka consumer lag > 1M messages | Stale data processing |
| Memory Pressure | RabbitMQ memory alarm | Broker blocks publishers |
| Increased Latency | Messages taking minutes/hours to process | SLA violations, user complaints |
Mitigation Strategies
| Strategy | How It Works | Trade-offs | When to Use |
|---|---|---|---|
| Scale Consumers | Add more consumer instances | Costs more, limited by partitions | First line of defense |
| Increase Prefetch | Deliver more messages per consumer | More memory, longer rebalance | Fast processing, small messages |
| Optimize Processing | Make consumer faster (cache, batch DB) | Development time required | Inefficient consumer code |
| Throttle Producers | Rate limit message publishing | Reduces throughput, may block users | Protect downstream systems |
| Drop Messages | Discard low-priority messages | Data loss, business impact | Last resort, non-critical metrics |
| Priority Queue | Process high-priority messages first | Low-priority messages starved | Mixed criticality workload |
Queue Depth Monitoring
// CloudWatch alarm for SQS queue depth
{
"AlarmName": "HighQueueDepth",
"MetricName": "ApproximateNumberOfMessagesVisible",
"Namespace": "AWS/SQS",
"Statistic": "Average",
"Period": 300,
"EvaluationPeriods": 2,
"Threshold": 10000,
"ComparisonOperator": "GreaterThanThreshold",
"AlarmActions": ["arn:aws:sns:us-east-1:123:pagerduty"]
}
Auto-Scaling Consumers
Consumer Scaling Example (Kubernetes HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: consumer-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: message-consumer
minReplicas: 2
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: sqs_queue_depth
selector:
matchLabels:
queue_name: my-queue
target:
type: AverageValue
averageValue: "1000" # scale if >1000 msgs per pod
Answer: First, scale consumers horizontally if possible. Monitor consumer processing rate and errors. If scaling doesn't help, investigate: Are consumers slow (optimize)? Are messages failing (fix bugs)? Is downstream service slow (increase timeout, circuit breaker)? As last resort, throttle producers or use priority queue.
Patterns
1. Work Queue (Task Distribution)
Use case: Distribute tasks across multiple workers (image resize, report generation). Each message consumed by one worker only.
2. Pub/Sub (Event Broadcasting)
Use case: Broadcast events to multiple subscribers (user signup, order placed). Each subscriber gets copy of message.
3. Request-Reply
Use case: RPC over message queue. Client sends request, server replies to specified queue with correlation ID.
4. Priority Queue
// RabbitMQ priority queue
channel.queueDeclare("tasks", true, false, false,
Map.of("x-max-priority", 10));
// Publish with priority
channel.basicPublish("", "tasks",
new AMQP.BasicProperties.Builder().priority(5).build(),
message);
Use case: Premium users, urgent tasks, critical alerts processed first.
5. Delayed/Scheduled Messages
// SQS delay (up to 15 min)
sqs.sendMessage({
QueueUrl: queueUrl,
MessageBody: message,
DelaySeconds: 300 // 5 minutes
});
// RabbitMQ delayed exchange (plugin required)
channel.exchangeDeclare("delayed", "x-delayed-message", true,
Map.of("x-delayed-type", "direct"));
channel.basicPublish("delayed", routingKey,
new AMQP.BasicProperties.Builder()
.headers(Map.of("x-delay", 300000)) // 5 min in ms
.build(),
message);
Use case: Scheduled jobs, retry with backoff, reminder emails.
Pattern Comparison
| Pattern | Consumers | Message Delivery | Use Case |
|---|---|---|---|
| Work Queue | Multiple (competing) | Each message to one consumer | Load distribution, parallel tasks |
| Pub/Sub | Multiple (independent) | Each message to all consumers | Event broadcasting, notifications |
| Request-Reply | One (RPC) | Request to one, reply to requester | Async RPC, microservice communication |
| Priority Queue | Multiple (competing) | High priority messages first | Mixed criticality workloads |
| Delayed | Multiple (competing) | After specified delay | Scheduled tasks, retry with backoff |
Answer: Use pub/sub pattern. Upload service publishes to fanout exchange (RabbitMQ) or SNS topic (AWS). Three queues bound to exchange: virus-scan-queue, thumbnail-queue, metadata-queue. Each has dedicated consumer service. All process same upload independently in parallel.
Comparison: RabbitMQ vs SQS vs Others
| Feature | RabbitMQ | AWS SQS | ActiveMQ | NATS |
|---|---|---|---|---|
| Protocol | AMQP 0.9.1 | HTTP/HTTPS API | AMQP, STOMP, MQTT | NATS protocol (TCP) |
| Hosting | Self-hosted | Fully managed (AWS) | Self-hosted | Self-hosted or cloud |
| Routing | Advanced (exchanges, bindings) | Simple (queue only) | Advanced (topics, selectors) | Subject-based |
| Ordering | Per consumer (prefetch=1) | FIFO queues only | Per consumer | No guarantee |
| Max Throughput | 50k msg/sec (single queue) | Unlimited (Standard) | 10k msg/sec | Millions/sec |
| Message Size | 512 MB (configurable) | 256 KB | 1 GB | 1 MB |
| Delivery Guarantee | At-least-once | At-least-once (Standard), exactly-once (FIFO) | At-least-once | At-most-once (default) |
| Message Persistence | Optional (durable queues) | Always (4-14 days) | Optional (persistent delivery) | Optional (JetStream) |
| Dead Letter Queue | Yes | Yes | Yes | No (app-level) |
| Priority Queue | Yes | No (use separate queues) | Yes | No |
| Delayed Messages | Plugin required | Yes (0-15 min) | Yes | No |
| High Availability | Quorum queues (Raft) | Built-in (multi-AZ) | Master-slave replication | Clustering |
| Management UI | Excellent (built-in) | AWS Console | Web console | Third-party |
| Complexity | High (rich features) | Low (simple) | Medium | Very low |
| Operational Burden | High (manage cluster) | None (serverless) | High | Medium |
| Cost | EC2/server costs | $0.40 per million requests | EC2/server costs | EC2/server costs |
| Best For | Complex routing, microservices | AWS workloads, simple queuing | Enterprise Java apps | Ultra-low latency, IoT |
When to Choose Each
| Choose | When | Example Use Case |
|---|---|---|
| RabbitMQ | Complex routing, advanced features, any cloud | Microservices with topic-based routing, multi-cloud |
| SQS | AWS-only, simple queuing, zero ops | Lambda processing, AWS-native apps |
| ActiveMQ | Enterprise Java, JMS requirement | Legacy enterprise apps, Spring Integration |
| NATS | Ultra-low latency, high throughput, simple pub/sub | Real-time streaming, IoT telemetry, edge computing |
Answer: Depends on context. SQS if AWS-native, want zero ops, simple use case (work queue). RabbitMQ if need complex routing (topic exchanges), multi-cloud, advanced features (priority, delayed messages), or already have ops expertise. SQS easier to start, RabbitMQ more flexible at scale.
Queue vs Stream: When to Use What
Message queues (RabbitMQ, SQS) and event streams (Kafka, Kinesis) solve different problems. Choosing wrong one causes pain.
Core Differences
| Aspect | Message Queue | Event Stream |
|---|---|---|
| Message Lifecycle | Deleted after consumption | Retained (days/weeks), replayable |
| Consumption Model | Competing consumers (each message to one) | Independent consumers (each reads all) |
| Ordering | Difficult at scale (FIFO limited throughput) | Guaranteed within partition |
| Consumer Tracking | Broker tracks (ACKs) | Consumer tracks (offsets) |
| Replay | No (message deleted) | Yes (rewind to any offset) |
| Primary Use | Task distribution, RPC | Event sourcing, analytics, audit logs |
Decision Matrix
| Requirement | Queue | Stream | Reasoning |
|---|---|---|---|
| Task distribution | ā | ā | Each task processed once by one worker |
| Event broadcasting | ā | ā | Both support pub/sub (fanout or topics) |
| Strict ordering at scale | ā | ā | Kafka partition ordering scales linearly |
| Message replay | ā | ā | Streams retain data, queues delete |
| Multiple independent consumers | ā ļø | ā | Queues need separate queue per consumer |
| Low operational complexity | ā | ā | SQS is serverless, Kafka requires cluster |
| Short message retention | ā | ā ļø | Queues auto-delete, streams retain forever |
| High throughput (millions/sec) | ā ļø | ā | Kafka handles 100k+ msg/sec per partition |
| Audit log/event sourcing | ā | ā | Need permanent, replayable history |
| Request-reply pattern | ā | ā | Queues support reply-to pattern natively |
Use Case Examples
| Use Case | Technology | Why |
|---|---|---|
| Image resize pipeline | SQS / RabbitMQ | Task distribution, each image processed once |
| User activity tracking | Kafka / Kinesis | Multiple consumers (analytics, ML, storage), replay |
| Order processing | SQS FIFO | Strict ordering per user, task distribution |
| Event sourcing | Kafka | Permanent event log, rebuild state from history |
| Email notifications | SQS / RabbitMQ | Simple task queue, no need to replay |
| IoT sensor data | Kinesis / Kafka | High throughput, multiple consumers, time-series |
| Microservice communication | RabbitMQ | Complex routing (topics), request-reply |
| CDC (change data capture) | Kafka | DB changes as events, multiple downstream systems |
| Background jobs (cron) | SQS / RabbitMQ | Simple task execution, no replay needed |
| Real-time analytics pipeline | Kafka | Multiple consumers, windowed aggregations, replay |
Hybrid Pattern: Queue + Stream
Pattern: Use Kafka for event distribution and replay, then push tasks to SQS for worker distribution. Best of both: event history + task distribution.
Answer: Depends on scale and complexity. For simple payment queue (100s/sec), SQS FIFO - easy ops, strict ordering, exactly-once. For high-scale (1000s/sec) or need audit trail, use Kafka - partition by account for ordering, retain payment events forever for compliance, multiple consumers (fraud detection, reporting, notifications).
Benchmarks & Real-World Architecture
Performance Benchmarks
| System | Hardware | Throughput | P99 Latency | Message Size | Notes |
|---|---|---|---|---|---|
| RabbitMQ (persistent) | m5.xlarge (4 vCPU) | 50K msgs/sec | 5ms | 1KB | Durable queues, ack after persist |
| RabbitMQ (non-persistent) | m5.xlarge (4 vCPU) | 150K msgs/sec | 2ms | 1KB | In-memory only, higher risk |
| SQS Standard | Managed (unlimited) | Unlimited | ~10ms send, ~20ms receive | 256KB max | $0.40 per million requests |
| SQS FIFO | Managed | 3000 msgs/sec | ~15ms send | 256KB max | With batching: 30K msgs/sec |
Real Architecture Example: Uber-Style Ride System
Scenario: Process ride events (trip start, payment, rating) with fan-out to multiple downstream services.
| Component | Technology | Details |
|---|---|---|
| Producer | Trip Service | Publishes ride events (trip_started, trip_completed) to RabbitMQ topic exchange |
| Queue | RabbitMQ (topic exchange) | 10K msgs/sec throughput. Fan-out via bindings to 3 queues. |
| Consumers | Pricing Worker, Analytics Worker, Notification Worker | Each reads from dedicated queue. Independent scaling (K8s HPA). |
| Monitoring | Queue depth metric | If depth > 1000 ā scale consumers via Kubernetes HPA |
// RabbitMQ flow
Trip Service ā Topic Exchange (ride.events)
ā (binding: ride.*)
āāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāā
ā ā ā
pricing-queue analytics-queue notification-queue
ā ā ā
Pricing Worker Analytics Worker Notification Worker
(5 instances) (3 instances) (2 instances)
Failure Scenario Timeline
Scenario: Consumer crashes before ACKing message. What happens?
| Time | Event | System State |
|---|---|---|
| T=0s | Consumer A receives message, starts processing | Message invisible (visibility timeout 30s in SQS, or unACKed in RabbitMQ) |
| T=15s | Consumer A crashes (OOM, exception, pod killed) | Message not ACKed, still invisible |
| T=30s | Visibility timeout expires (SQS) or connection lost (RabbitMQ) | Message becomes visible again in queue |
| T=31s | Consumer B receives same message for redelivery | At-least-once delivery (Consumer B must be idempotent) |
| T=45s | Consumer B processes successfully, ACKs | Message deleted from queue |
| If Consumer B also fails | After 3 attempts (T=91s total), message sent to DLQ | Poison message quarantined for manual investigation |
Cost & Throughput Comparison
| System | Monthly Cost (1M msgs) | Monthly Cost (1B msgs) | Throughput Limit |
|---|---|---|---|
| SQS Standard | $0.40 | $400 | Unlimited |
| SQS FIFO | $0.40 | $400 | 3K/sec (30K batched) |
| RabbitMQ (self-hosted) | ~$50 (m5.xlarge) | ~$200 (m5.2xlarge cluster) | 50-150K/sec per node |