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

graph TB subgraph "Without Queue - Tight Coupling" A1[Web Server] -->|Synchronous| B1[Email Service] A1 -->|Synchronous| C1[SMS Service] A1 -->|Synchronous| D1[Analytics] A1 -->|Wait for all| E1[Database] end
graph TB subgraph "With Queue - Decoupled" A2[Web Server] -->|Publish| Q[Message Queue] Q -->|Subscribe| B2[Email Worker] Q -->|Subscribe| C2[SMS Worker] Q -->|Subscribe| D2[Analytics Worker] A2 -->|Direct write| E2[Database] B2 & C2 & D2 --> E2 end
Interview Q: "Why use a message queue instead of direct service calls?"
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

sequenceDiagram participant P as Producer participant Q as Queue participant C as Consumer P->>Q: Publish message Note over Q: Message persisted C->>Q: Poll for messages Q->>C: Deliver message Note over C: Process message C->>Q: ACK (success) Note over Q: Delete message
Interview Q: "What happens if consumer crashes before ACKing?"
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
Interview Q: "How would you implement exactly-once processing?"
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

graph LR P1[Producer 1] --> E[Exchange] P2[Producer 2] --> E E -->|Binding| Q1[Queue 1] E -->|Binding| Q2[Queue 2] E -->|Binding| Q3[Queue 3] Q1 --> C1[Consumer 1] Q2 --> C2[Consumer 2] Q3 --> C3[Consumer 3]

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
Interview Q: "How does RabbitMQ achieve high availability?"
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

sequenceDiagram participant C1 as Consumer 1 participant Q as SQS Queue participant C2 as Consumer 2 C1->>Q: ReceiveMessage Q->>C1: Message (visible timeout: 30s) Note over Q: Message invisible for 30s Note over C1: Processing... alt Success within 30s C1->>Q: DeleteMessage Note over Q: Message deleted else Timeout (no delete) Note over Q: 30s elapsed, message visible again C2->>Q: ReceiveMessage Q->>C2: Same message (redelivery) end

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
  }
}
Interview Q: "When would you use SQS Standard vs FIFO?"
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

graph TB P[Producer] -->|userId % 3 = 0| P0[Partition 0] P -->|userId % 3 = 1| P1[Partition 1] P -->|userId % 3 = 2| P2[Partition 2] P0 --> C0[Consumer 0] P1 --> C1[Consumer 1] P2 --> C2[Consumer 2] Note0[Ordered within partition] Note1[Parallel across partitions]

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
}
Interview Q: "How would you ensure ordering for user events at scale?"
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

graph LR P[Producer] --> Q[Main Queue] Q --> C[Consumer] C -->|Success| D[Delete] C -->|Failure| R[Retry] R -->|Attempt 1| C R -->|Attempt 2| C R -->|Attempt 3| C R -->|Max retries exceeded| DLQ[Dead Letter Queue] DLQ --> M[Manual Review/Fix]

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);
}
Interview Q: "How would you handle a DLQ with 10,000 messages?"
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

graph LR Q[Queue] -->|Monitor depth| CW[CloudWatch] CW -->|Depth > 1000| AS[Auto Scaling] AS -->|Scale out| C1[Consumer 1] AS -->|Scale out| C2[Consumer 2] AS -->|Scale out| C3[Consumer 3] CW -->|Depth < 100| AS2[Auto Scaling] AS2 -->|Scale in| C1

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
Interview Q: "Queue depth is growing, what do you do?"
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)

graph LR P[Producer] --> Q[Queue] Q --> C1[Worker 1] Q --> C2[Worker 2] Q --> C3[Worker 3]

Use case: Distribute tasks across multiple workers (image resize, report generation). Each message consumed by one worker only.

2. Pub/Sub (Event Broadcasting)

graph LR P[Publisher] --> T[Topic/Fanout Exchange] T --> Q1[Email Queue] T --> Q2[Analytics Queue] T --> Q3[Audit Queue] Q1 --> C1[Email Service] Q2 --> C2[Analytics Service] Q3 --> C3[Audit Service]

Use case: Broadcast events to multiple subscribers (user signup, order placed). Each subscriber gets copy of message.

3. Request-Reply

sequenceDiagram participant Client participant RequestQ as Request Queue participant Server participant ReplyQ as Reply Queue Client->>RequestQ: Request (replyTo: replyQueue, correlationId: 123) Server->>RequestQ: Receive request Server->>ReplyQ: Response (correlationId: 123) Client->>ReplyQ: Receive response (match correlationId)

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
Interview Q: "Design a system where user uploads trigger 3 different processing tasks."
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
Interview Q: "RabbitMQ vs SQS - which would you choose?"
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

graph LR E[Event Source] --> K[Kafka Topic] K --> C1[Analytics Consumer] K --> C2[ML Pipeline] K --> C3[Stream Processor] C3 --> S[SQS Queue] S --> W1[Worker 1] S --> W2[Worker 2]

Pattern: Use Kafka for event distribution and replay, then push tasks to SQS for worker distribution. Best of both: event history + task distribution.

Interview Q: "Would you use Kafka or SQS for a payment processing system?"
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
Interview Tip: Real production numbers are crucial. RabbitMQ m5.xlarge = 50K msgs/sec persistent, 150K non-persistent. SQS Standard = unlimited throughput but ~10ms send latency. SQS FIFO limited to 3K/sec (use batching for 30K/sec). Failure timeline: Consumer crash at T=0 → visibility timeout at T=30s → redelivery to Consumer B at T=31s → DLQ after 3 attempts at T=91s. Uber-style fan-out: 10K msgs/sec → 3 queues → independent worker pools with HPA.