CrackedRuby CrackedRuby

Overview

Message queues provide asynchronous communication between distributed system components by storing messages in an intermediate buffer until the receiving component can process them. This decoupling allows producers to send messages without waiting for consumers to be available, and consumers to process messages at their own pace without blocking producers.

The message queue pattern emerged from the need to handle workloads that don't require immediate processing and to prevent system failures from cascading across components. When a web application needs to send an email, resize an image, or update a recommendation engine, executing these tasks synchronously would slow down the user's request. Message queues move these operations into background jobs that execute independently of the main application flow.

A message queue system contains three primary components: producers that generate messages, a broker that stores and routes messages, and consumers that process messages. The broker ensures messages persist even if consumers are temporarily unavailable, providing durability guarantees that prevent data loss during system failures.

# Producer sends a message
MessageQueue.publish('email.send', {
  to: 'user@example.com',
  subject: 'Welcome',
  body: 'Thank you for signing up'
})

# Consumer processes messages
MessageQueue.subscribe('email.send') do |message|
  EmailService.deliver(message[:to], message[:subject], message[:body])
end

Message queues solve several distributed systems problems: they absorb traffic spikes by buffering incoming work, enable horizontal scaling of workers, provide fault tolerance through message persistence, and allow independent deployment and scaling of system components. The pattern transforms synchronous dependencies into asynchronous workflows, reducing coupling and increasing system resilience.

Key Principles

Message queues operate on a producer-consumer model where producers generate messages without knowledge of who will process them, and consumers process messages without knowledge of who generated them. This separation creates loose coupling between system components, allowing each to evolve independently.

Message Structure: Each message contains a payload (the data to process) and metadata (routing information, priority, timestamps). Messages are immutable once created. The payload typically uses a serialization format like JSON, MessagePack, or Protocol Buffers. Metadata includes the routing key that determines which queue receives the message, delivery mode flags, expiration times, and correlation identifiers for tracking related messages.

Delivery Guarantees: Message queues provide different delivery semantics. At-most-once delivery sends each message once without confirmation, accepting potential message loss for lower latency. At-least-once delivery confirms receipt and retries on failure, ensuring no messages are lost but potentially delivering duplicates. Exactly-once delivery guarantees each message processes once through distributed transactions or idempotency mechanisms, though implementing true exactly-once semantics requires careful coordination between the broker and consumers.

Acknowledgment Pattern: Consumers explicitly acknowledge messages after successful processing. The broker maintains messages in an unacknowledged state until receiving confirmation, allowing message redelivery if a consumer fails. This acknowledgment mechanism provides fault tolerance without requiring producers to track consumer state.

# Manual acknowledgment pattern
consumer.subscribe('image.resize', manual_ack: true) do |delivery_info, properties, payload|
  begin
    image = JSON.parse(payload)
    ImageProcessor.resize(image['path'], image['dimensions'])
    consumer.ack(delivery_info.delivery_tag)
  rescue => e
    consumer.nack(delivery_info.delivery_tag, false, true) # reject and requeue
  end
end

Message Ordering: Queues typically preserve FIFO ordering for messages sent to the same queue, but ordering across multiple queues or consumers requires additional coordination. Partitioned queues assign messages to specific partitions based on a key, ensuring messages with the same key process in order. Global ordering across all messages often requires single-consumer processing, sacrificing parallel execution for ordering guarantees.

Durability and Persistence: Messages can exist only in memory for maximum throughput or persist to disk for durability. Durable messages survive broker restarts but incur write latency. Transient messages provide lower latency but risk loss during failures. The durability choice depends on whether the application can tolerate message loss or requires guaranteed delivery.

Dead Letter Queues: When messages fail processing repeatedly, queues route them to a dead letter queue rather than retrying indefinitely. This prevents poison messages from blocking queue processing and provides a mechanism for manual inspection and remediation of problematic messages.

# Dead letter queue configuration
queue = channel.queue('orders.process', arguments: {
  'x-dead-letter-exchange' => 'orders.dlx',
  'x-message-ttl' => 86400000, # 24 hours
  'x-max-length' => 10000
})

Backpressure: When consumers cannot keep pace with incoming messages, queues implement backpressure through prefetch limits that control how many unacknowledged messages a consumer can hold. This prevents overwhelming slow consumers and provides time-based flow control across the system.

Implementation Approaches

Message queue implementations follow several architectural patterns, each optimized for different use cases and consistency requirements.

Work Queue Pattern: Multiple workers consume from a single queue, distributing tasks across available processors. The broker assigns each message to exactly one consumer using round-robin or weighted distribution. This pattern scales horizontal processing capacity and provides automatic load balancing. Workers compete for messages, with faster workers processing more tasks. The pattern suits CPU-intensive work that can execute independently, like image processing, report generation, or data transformation.

Publish-Subscribe Pattern: Producers publish messages to topics, and multiple subscribers receive copies of each message. The broker maintains separate queues for each subscriber, copying messages to all interested consumers. This pattern distributes events to multiple systems that need to react to the same occurrence, like updating a cache, sending notifications, and logging analytics when a user signs up.

# Publish-subscribe with topic exchange
exchange = channel.topic('user.events')

# Multiple consumers receive the same messages
analytics_queue = channel.queue('analytics.user_events')
analytics_queue.bind(exchange, routing_key: 'user.#')

notification_queue = channel.queue('notifications.user_events')
notification_queue.bind(exchange, routing_key: 'user.signup')

cache_queue = channel.queue('cache.user_events')
cache_queue.bind(exchange, routing_key: 'user.*')

Request-Reply Pattern: A client sends a message and waits for a response, implementing synchronous RPC semantics over asynchronous messaging. The request includes a reply-to queue and correlation identifier. The server processes the request and sends the response to the specified reply queue with the matching correlation ID. This pattern combines asynchronous messaging benefits with synchronous semantics when the client needs the result before continuing.

Priority Queue Pattern: Messages carry priority values, and the broker delivers high-priority messages before low-priority ones within the same queue. This ensures time-sensitive operations process before routine background tasks. Priority queues require careful design to prevent starvation of low-priority messages.

Competing Consumers Pattern: Multiple consumer instances process messages from the same queue, providing fault tolerance and load distribution. If one consumer fails, others continue processing. The broker distributes messages across consumers and redelivers messages from failed consumers. This pattern combines redundancy with parallel processing.

Message Routing: Brokers route messages based on patterns matching the routing key. Direct routing delivers messages to queues with exact key matches. Topic routing uses wildcard patterns for flexible subscription. Fanout routing sends messages to all bound queues regardless of routing keys. Header routing examines message headers for routing decisions.

# Topic-based routing with patterns
exchange = channel.topic('logs')

# Receives all error logs from any service
error_queue = channel.queue('errors')
error_queue.bind(exchange, routing_key: '*.error')

# Receives all logs from the payment service
payment_queue = channel.queue('payment_logs')
payment_queue.bind(exchange, routing_key: 'payment.*')

# Receives only critical errors
critical_queue = channel.queue('critical')
critical_queue.bind(exchange, routing_key: '*.error.critical')

Ruby Implementation

Ruby applications interact with message queues through client libraries that abstract broker-specific protocols. The ecosystem includes gems for job processing, broker clients, and complete background job frameworks.

Sidekiq: The most popular Ruby background job processor uses Redis as its message broker. Sidekiq provides job queuing, scheduling, retries, and a web interface for monitoring. Jobs are Ruby classes with a perform method. Sidekiq serializes job arguments to JSON and stores them in Redis lists.

# Define a job
class ImageResizeWorker
  include Sidekiq::Worker
  sidekiq_options queue: 'images', retry: 5

  def perform(image_id, width, height)
    image = Image.find(image_id)
    resized = ImageProcessor.resize(image.path, width, height)
    image.update(resized_path: resized)
  end
end

# Enqueue a job
ImageResizeWorker.perform_async(image_id, 800, 600)

# Schedule for later
ImageResizeWorker.perform_in(1.hour, image_id, 800, 600)
ImageResizeWorker.perform_at(tomorrow_noon, image_id, 800, 600)

Sidekiq workers poll Redis for jobs, deserialize the arguments, and execute the perform method. Failed jobs automatically retry with exponential backoff. The retry count, delay, and dead queue behavior are configurable per job class. Sidekiq uses connection pooling and multi-threading to maximize throughput from a single process.

Bunny: A RabbitMQ client for Ruby that implements AMQP 0.9.1. Bunny provides low-level control over exchanges, queues, bindings, and message properties. Applications create channels, declare queues and exchanges, publish messages, and consume with callbacks or blocking iteration.

require 'bunny'

connection = Bunny.new(host: 'localhost', port: 5672)
connection.start
channel = connection.create_channel

# Declare durable queue
queue = channel.queue('orders', durable: true)

# Publish with persistence
channel.default_exchange.publish(
  order_data.to_json,
  routing_key: queue.name,
  persistent: true,
  content_type: 'application/json',
  timestamp: Time.now.to_i
)

# Consume with manual acknowledgment
queue.subscribe(manual_ack: true, block: true) do |delivery_info, properties, body|
  order = JSON.parse(body)
  begin
    OrderProcessor.process(order)
    channel.ack(delivery_info.delivery_tag)
  rescue => error
    channel.nack(delivery_info.delivery_tag, false, false)
    ErrorLogger.log(error, order)
  end
end

Bunny supports publisher confirms for guaranteed delivery, consumer prefetch for flow control, and message TTL for automatic expiration. The library handles connection recovery, provides thread-safe channel operations, and exposes RabbitMQ-specific features like exchange-to-exchange bindings and alternate exchanges.

Sneakers: A background processing framework built on RabbitMQ and Bunny. Sneakers provides a Sidekiq-like API for defining workers while using RabbitMQ as the broker. Workers declare which queues they consume from, and Sneakers handles thread pools, connection management, and graceful shutdown.

class EmailWorker
  include Sneakers::Worker
  from_queue 'emails',
    threads: 10,
    prefetch: 10,
    timeout_job_after: 60,
    ack: true

  def work(msg)
    data = JSON.parse(msg)
    EmailService.deliver(data['to'], data['subject'], data['body'])
    ack!
  rescue => error
    ErrorLogger.log(error, msg)
    reject!
  end
end

Resque: Redis-backed job processing inspired by DelayedJob. Resque uses Redis lists for queues and Ruby processes for workers. Each job is a Ruby class with a perform class method. Resque prioritizes reliability over throughput, processing one job per worker process.

class ReportGenerator
  @queue = :reports

  def self.perform(user_id, report_type, start_date, end_date)
    user = User.find(user_id)
    report = ReportBuilder.generate(report_type, start_date, end_date)
    ReportMailer.deliver(user.email, report)
  end
end

# Enqueue
Resque.enqueue(ReportGenerator, user.id, 'sales', Date.today - 30, Date.today)

AWS SDK for SQS: Amazon's simple queue service client provides managed message queuing without running infrastructure. The AWS SDK handles authentication, request signing, and retry logic. SQS offers standard queues with high throughput and best-effort ordering, and FIFO queues with guaranteed ordering and exactly-once processing.

require 'aws-sdk-sqs'

sqs = Aws::SQS::Client.new(region: 'us-east-1')
queue_url = sqs.get_queue_url(queue_name: 'orders').queue_url

# Send message
sqs.send_message(
  queue_url: queue_url,
  message_body: order.to_json,
  message_attributes: {
    'Priority' => { string_value: 'high', data_type: 'String' },
    'Source' => { string_value: 'web', data_type: 'String' }
  }
)

# Receive and process
loop do
  response = sqs.receive_message(
    queue_url: queue_url,
    max_number_of_messages: 10,
    wait_time_seconds: 20,
    visibility_timeout: 30
  )

  response.messages.each do |message|
    process_order(JSON.parse(message.body))
    sqs.delete_message(queue_url: queue_url, receipt_handle: message.receipt_handle)
  end
end

Shoryuken: An SQS-based background processing library that provides a Sidekiq-like interface for AWS SQS. Shoryuken handles polling, visibility timeout extension, and automatic deletion of processed messages.

Design Considerations

Message queues introduce asynchronous complexity that requires careful evaluation against application requirements. The decision to adopt message queues depends on whether the benefits of decoupling and scalability outweigh the operational complexity.

Synchronous vs Asynchronous Processing: Synchronous processing provides immediate feedback and simpler error handling but couples components tightly and limits scalability. Asynchronous processing through queues decouples components and improves throughput but complicates error handling and makes debugging harder. Use synchronous processing when the user needs immediate results or when operations must complete in a specific order. Use message queues when operations can execute independently, when traffic varies significantly, or when processing time exceeds acceptable request latency.

Message Queue vs Direct API Calls: Direct API calls provide strong consistency and immediate error feedback but create tight coupling and fail when the called service is unavailable. Message queues provide loose coupling and buffer capacity spikes but require eventual consistency thinking and more complex error tracking. Choose direct calls for operations requiring immediate confirmation, like payment processing or inventory reservation. Choose message queues for operations that can complete asynchronously, like sending emails, generating reports, or updating analytics.

Broker Selection Criteria: Different message brokers optimize for different characteristics. Redis-based queues (Sidekiq, Resque) provide simple deployment and high throughput but limited durability guarantees and routing features. RabbitMQ provides strong durability, sophisticated routing, and clustering but requires more operational knowledge. Cloud-managed services (SQS, Cloud Pub/Sub) eliminate infrastructure management but increase per-message costs and introduce vendor lock-in. Apache Kafka provides high-throughput stream processing but adds significant operational complexity.

Choose Redis for job queues in applications already using Redis, when message loss during failures is acceptable, or when throughput is the primary concern. Choose RabbitMQ when durability and complex routing are required, when running on-premises infrastructure, or when needing sophisticated queue features. Choose managed services when minimizing operational burden justifies the cost, when scaling unpredictably, or when integrating with cloud-native applications.

Queue Granularity: Applications must decide between few queues with high throughput and many specialized queues with targeted processing. Few queues simplify operations and reduce connection overhead but make priority handling and scaling more difficult. Many queues enable fine-grained priority and scaling but increase complexity and broker resource usage.

Create separate queues for different priority levels, different processing characteristics (CPU-bound vs I/O-bound), and different retry behaviors. Avoid creating queues per tenant or per entity, which scales poorly. Balance queue count against operational complexity and broker capabilities.

Message Size Constraints: Large messages increase broker memory usage, network bandwidth, and processing latency. When messages exceed a few kilobytes, consider storing the data externally and passing only a reference through the queue. This pattern reduces broker load and enables processing large datasets without queue size limits.

# Store large payload externally
s3_key = S3Service.upload(large_dataset)
VideoTranscodeWorker.perform_async(s3_key)

# Worker retrieves payload
def perform(s3_key)
  dataset = S3Service.download(s3_key)
  process(dataset)
  S3Service.delete(s3_key)
end

Idempotency Requirements: At-least-once delivery means consumers may process messages multiple times. Operations must either be naturally idempotent or implement idempotency explicitly. Design message handlers to produce the same result when executed multiple times with the same input. Store message identifiers in a database to detect and skip duplicate processing, or use upsert operations that produce the same final state regardless of execution count.

Monitoring and Observability: Message queues hide processing behind asynchronous boundaries, making observability critical. Track queue depth, message age, processing time, error rates, and consumer lag. Set alerts for queue depth exceeding thresholds, old messages indicating stalled processing, and elevated error rates signaling systemic issues. Include correlation IDs in messages to trace work across queue boundaries.

Tools & Ecosystem

The message queue ecosystem includes brokers, client libraries, monitoring tools, and managed services that handle different aspects of asynchronous messaging.

RabbitMQ: An open-source message broker implementing AMQP with plugins for MQTT, STOMP, and other protocols. RabbitMQ provides durable queues, flexible routing through exchanges, clustering for high availability, and federation for distributed deployments. The management plugin offers a web UI for monitoring and administration. RabbitMQ excels at reliable message delivery with complex routing requirements and moderate throughput needs.

Apache Kafka: A distributed streaming platform designed for high-throughput event streaming. Kafka stores messages in partitioned, replicated logs rather than transient queues. Consumers track their offset in the log, enabling message replay and multiple consumer groups reading the same data at different rates. Kafka fits event sourcing, log aggregation, and stream processing use cases requiring message retention and replay capabilities.

Redis: An in-memory data store that provides list and pub/sub operations used for message queuing. Redis offers exceptional throughput and simple operations but limited durability and no complex routing. Redis-based queues work for job queues where occasional message loss during failures is acceptable and when Redis is already part of the infrastructure.

Amazon SQS: A managed message queue service that provides standard queues with high throughput and FIFO queues with ordering guarantees. SQS handles scaling, replication, and failure recovery automatically. The service charges per message, making it cost-effective for variable workloads and expensive for high-volume applications. SQS integrates natively with other AWS services and eliminates operational overhead.

Google Cloud Pub/Sub: A managed publish-subscribe service providing global message delivery with at-least-once semantics. Pub/Sub automatically scales and replicates messages across regions. The service supports push subscriptions that deliver messages to HTTP endpoints and pull subscriptions where consumers poll for messages. Pub/Sub fits cloud-native applications requiring global distribution and automatic scaling.

ActiveMQ: A Java-based message broker supporting JMS, AMQP, MQTT, and STOMP protocols. ActiveMQ provides clustering, master-slave failover, and message groups for ordered processing. The broker suits enterprise environments requiring JMS compatibility and multiple protocol support.

NATS: A lightweight message broker focused on simplicity and performance. NATS provides pub/sub messaging with optional persistence and streaming through NATS Streaming. The broker emphasizes low latency and high throughput for cloud-native microservices communication.

Monitoring Tools: Queue monitoring requires visibility into message flow, processing latency, and error rates. RabbitMQ provides a management plugin with metrics and a HTTP API. Prometheus exporters collect metrics from various brokers for time-series analysis. DataDog, New Relic, and AWS CloudWatch offer managed monitoring with alerting. Application Performance Monitoring tools trace requests across queue boundaries using correlation identifiers.

Ruby Gems: The Ruby ecosystem includes gems for different brokers and patterns. Bunny and March Hare (JRuby) provide RabbitMQ clients. Sidekiq, Resque, and DelayedJob offer background job processing. Sneakers combines RabbitMQ with background processing. Shoryuken and ActiveJob's SQS adapter integrate with Amazon SQS. Karafka provides Kafka consumer framework. Wisper implements in-process pub/sub for decoupled Ruby code.

Common Patterns

Message queue implementations follow established patterns that solve recurring distributed messaging problems.

Retry Pattern: Failed messages retry with exponential backoff to handle transient failures without overwhelming the system. The pattern increases delay between retries, giving temporary problems time to resolve. After exceeding maximum retries, messages move to a dead letter queue for manual investigation.

class DocumentProcessor
  include Sidekiq::Worker
  sidekiq_options retry: 10

  sidekiq_retry_in do |count|
    10 * (count + 1) # 10, 20, 30, 40... seconds
  end

  sidekiq_retries_exhausted do |msg, ex|
    Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{ex.message}"
    ErrorNotifier.alert(msg, ex)
  end

  def perform(document_id)
    DocumentService.process(document_id)
  end
end

Message Deduplication Pattern: When at-least-once delivery causes duplicate messages, track processed message identifiers to skip duplicates. Store message IDs in Redis or a database table with an expiration matching the maximum redelivery window. Before processing, check if the message ID exists; if so, acknowledge without processing.

def perform(message_id, data)
  return if REDIS.get("processed:#{message_id}")

  process_data(data)

  REDIS.setex("processed:#{message_id}", 86400, "1")
end

Saga Pattern: Coordinate multi-step processes across services using message choreography. Each service publishes events about its actions, and other services react by performing their steps and publishing their own events. This pattern implements distributed transactions without a central coordinator. If a step fails, compensating actions undo previous steps.

# Order service publishes order created event
EventBus.publish('order.created', {
  order_id: order.id,
  user_id: order.user_id,
  items: order.items
})

# Inventory service reserves items
EventBus.subscribe('order.created') do |event|
  if InventoryService.reserve(event[:items])
    EventBus.publish('inventory.reserved', event)
  else
    EventBus.publish('inventory.reservation_failed', event)
  end
end

# Payment service charges customer
EventBus.subscribe('inventory.reserved') do |event|
  if PaymentService.charge(event[:user_id], event[:total])
    EventBus.publish('payment.completed', event)
  else
    EventBus.publish('payment.failed', event)
  end
end

# Compensate on failure
EventBus.subscribe('payment.failed') do |event|
  InventoryService.release(event[:items])
  EventBus.publish('order.cancelled', event)
end

Outbox Pattern: Ensure messages publish when database changes commit by writing both the database record and the message to send within the same transaction. A separate process polls the outbox table and publishes messages to the queue, then marks them as sent. This guarantees messages send exactly once per database change without distributed transactions.

# Write both database record and outbox entry in transaction
ActiveRecord::Base.transaction do
  user = User.create!(email: email, name: name)
  
  Outbox.create!(
    aggregate_id: user.id,
    aggregate_type: 'User',
    event_type: 'user.created',
    payload: { id: user.id, email: user.email }
  )
end

# Separate process publishes outbox entries
loop do
  Outbox.unpublished.find_each do |entry|
    MessageQueue.publish(entry.event_type, entry.payload)
    entry.update!(published_at: Time.current)
  end
  sleep 1
end

Circuit Breaker Pattern: Protect failing services by stopping message processing when error rates exceed thresholds. The circuit breaker tracks failure rates and transitions between closed (normal), open (failing), and half-open (testing) states. When open, messages are rejected immediately without attempting processing. After a timeout, the circuit enters half-open state to test if the service recovered.

Message Filtering Pattern: Consumers filter messages based on content or metadata to process only relevant messages. Broker-side filtering using message properties reduces network traffic and consumer load. Client-side filtering provides more flexible logic but requires receiving all messages.

# Broker-side filtering with header matching
queue.bind(exchange, routing_key: 'notification.#', arguments: {
  'x-match' => 'all',
  'region' => 'us-east-1',
  'priority' => 'high'
})

# Client-side filtering
queue.subscribe do |delivery_info, properties, body|
  message = JSON.parse(body)
  next unless message['user_id'] == current_user_id
  next unless message['type'].in?(['urgent', 'important'])
  
  process_notification(message)
end

Claim Check Pattern: Handle large messages by storing the payload externally and passing a reference through the queue. The consumer uses the reference to retrieve the full payload from the storage system. This pattern reduces queue message size while enabling processing of arbitrarily large data.

Message Aggregation Pattern: Combine multiple related messages into a single batch before processing. An aggregator collects messages until reaching a count threshold or time window, then processes the batch. This pattern improves throughput for operations with high fixed costs per invocation.

Reference

Message Properties

Property Description Purpose
Message ID Unique identifier for the message Deduplication and tracking
Routing Key Determines message destination Exchange routing decisions
Timestamp When message was created Debugging and expiration
Delivery Mode Persistent or transient Durability guarantees
Priority Integer priority value Queue ordering decisions
Expiration Time-to-live in milliseconds Automatic message cleanup
Correlation ID Links related messages Request-reply pattern
Reply To Queue name for responses Request-reply pattern
Content Type MIME type of payload Serialization format
Content Encoding Payload encoding Compression indication

Delivery Guarantees

Guarantee Description Trade-offs
At-most-once Send without confirmation Lowest latency, potential message loss
At-least-once Confirm receipt, retry failures No message loss, potential duplicates
Exactly-once Guaranteed single delivery Highest latency, complex implementation

Queue Configuration Options

Option Description Use Case
Durable Queue survives broker restart Production workloads
Auto-delete Queue deletes when last consumer disconnects Temporary queues
Exclusive Only one connection can access Single consumer guarantee
Max length Maximum messages in queue Prevent memory exhaustion
Message TTL Time messages remain in queue Expire stale messages
Dead letter exchange Where rejected messages go Handle poison messages
Max priority Priority level range Priority queue implementation

Consumer Configuration

Setting Description Impact
Prefetch count Unacknowledged messages per consumer Flow control and distribution
Auto-ack Automatic acknowledgment Throughput vs reliability
Concurrency Parallel message handlers CPU utilization
Timeout Maximum processing time Hung worker detection
Retry count Maximum processing attempts Error handling

Ruby Queue Libraries Comparison

Library Broker Durability Routing Complexity
Sidekiq Redis Low None Low
Resque Redis Low None Low
Sneakers RabbitMQ High Advanced Medium
Bunny RabbitMQ High Advanced Medium
Shoryuken SQS High Basic Low
Karafka Kafka High Partitions High

Message Broker Feature Comparison

Feature RabbitMQ Redis Kafka SQS
Durability High Low High High
Throughput Medium High Very High Medium
Routing Advanced None Partitions Basic
Ordering Per queue Per list Per partition FIFO only
Persistence Disk Optional Log Managed
Management Self-hosted Self-hosted Self-hosted Managed

Monitoring Metrics

Metric Description Alert Threshold
Queue depth Messages waiting Growing over time
Message age Oldest message time Exceeds processing SLA
Processing time Time to process message Exceeds expected duration
Error rate Failed messages percentage Above baseline
Consumer lag Messages behind current Increasing continuously
Throughput Messages per second Below expected rate
Connection count Active connections Near broker limit

Common Message Patterns

Pattern Use Case Implementation
Work Queue Distribute tasks Multiple consumers, single queue
Pub/Sub Event broadcasting Topic exchange, multiple queues
RPC Synchronous over async Reply-to queue, correlation ID
Priority Order by importance Priority queue configuration
Routing Conditional delivery Topic or header routing
Delayed Schedule future execution Delayed exchange plugin

Sidekiq Job Options

Option Description Default
queue Queue name default
retry Enable retry with count true (25)
backtrace Include backtrace in errors false
dead Send to dead set when exhausted true
unique_for Prevent duplicate jobs None
tags Metadata for filtering Empty array

RabbitMQ Exchange Types

Type Routing Behavior Use Case
Direct Exact routing key match Point-to-point messaging
Topic Pattern matching with wildcards Flexible pub/sub
Fanout Broadcasts to all queues Event broadcasting
Headers Match message headers Content-based routing