The Complete RabbitMQ Guide — Features, Patterns & Best Practices

AMQP fundamentals, exchange types, reliability mechanisms, clustering, and messaging patterns.

🌱 Seedling·created: ·category:Distributed Systems

A deep, practitioner-level reference covering AMQP fundamentals, exchange types, reliability mechanisms, clustering/HA, messaging patterns, performance tuning, and production best practices.


Table of Contents

  1. Core Concepts
  2. Exchange Types
  3. Queues: Types & Properties
  4. Message Properties & Delivery Semantics
  5. Publisher Confirms & Reliable Publishing
  6. Consumer Acknowledgments & Prefetch (QoS)
  7. Dead Letter Exchanges (DLX)
  8. TTL: Message & Queue
  9. Delayed Messages
  10. Priority Queues
  11. Clustering & High Availability
  12. Federation & Shovel
  13. Security
  14. Management, Monitoring & Observability
  15. Messaging Patterns
  16. Connection & Channel Management Best Practices
  17. Idempotency & Error Handling
  18. Performance Tuning
  19. Common Pitfalls
  20. Quick Reference Cheat Sheet

1. Core Concepts

RabbitMQ implements AMQP 0-9-1 (with plugins for MQTT, STOMP, and AMQP 1.0). The core abstractions:

  • Producer — publishes messages to an Exchange (never directly to a queue).
  • Exchange — routes messages to one or more queues based on rules (bindings + routing keys).
  • Binding — a link between an exchange and a queue, optionally with a routing key or arguments.
  • Queue — an ordered buffer that stores messages until consumed.
  • Consumer — subscribes to a queue and processes messages.
  • Virtual Host (vhost) — a logical namespace isolating exchanges, queues, and permissions (multi-tenancy).
  • Connection — a TCP connection to the broker (expensive to create; long-lived).
  • Channel — a lightweight virtual connection multiplexed over a single TCP connection (cheap; one per thread/task).

Golden rule: Producers never publish “to a queue.” They always publish to an exchange with a routing key. The exchange decides where the message goes.

Producer → Exchange → (Binding + Routing Key) → Queue → Consumer

2. Exchange Types

2.1 Direct Exchange

Routes messages to queues whose binding key exactly matches the message’s routing key.

Exchange: orders.direct
Binding: routing_key = "order.created" → Queue: orders.created.q
Binding: routing_key = "order.cancelled" → Queue: orders.cancelled.q

Use case: Task routing where the target is deterministic (e.g., routing by event type).

2.2 Fanout Exchange

Broadcasts to all bound queues, ignoring the routing key entirely.

Use case: Pub/Sub — notifications, cache invalidation, broadcasting events to multiple independent services.

Exchange: notifications.fanout
→ Queue: email.service.q
→ Queue: sms.service.q
→ Queue: audit.log.q

2.3 Topic Exchange

Routes based on pattern matching on the routing key using wildcards:

  • * matches exactly one word
  • # matches zero or more words
Routing key format: <region>.<severity>.<service>

Binding: "eu.*.payments"    matches "eu.error.payments"
Binding: "*.critical.#"     matches "us.critical.payments.timeout"
Binding: "eu.#"             matches everything starting with "eu."

Use case: Flexible event routing — logging systems, multi-dimensional filtering.

2.4 Headers Exchange

Routes based on message header attributes instead of the routing key. Uses x-match argument:

  • x-match: all — all headers must match (AND)
  • x-match: any — at least one header must match (OR)
{
  "x-match": "all",
  "format": "pdf",
  "type": "report"
}

Use case: Rarely used in practice — routing key patterns (topic) usually suffice and are faster. Use headers exchange only when routing criteria don’t fit naturally into a string key.

2.5 Default (Nameless) Exchange

A special direct exchange ("") that every queue is automatically bound to, using the queue name as the routing key. Publishing with routing_key = "my_queue" to "" delivers directly to my_queue. Convenient for simple point-to-point, but avoid over-relying on it in larger systems — explicit exchanges make routing topology visible and evolvable.

2.6 Alternate Exchange (AE)

An exchange configured to receive messages that couldn’t be routed anywhere (no matching binding). Prevents silent message loss.

rabbitmqctl set_policy AE-orders "^orders\." '{"alternate-exchange":"orders.unrouted"}' --apply-to exchanges

3. Queues: Types & Properties

3.1 Classic Queues

The original queue type. Single-node data structure, replicated via the deprecated classic mirrored queues (mirrored via ha-mode policy). Being phased out for HA in favor of quorum queues.

Built on the Raft consensus algorithm. Data-safety-first design:

  • Always replicated across multiple nodes (minimum recommended: 3, odd number)
  • No “flapping” mirror re-sync issues that classic mirrored queues suffered from
  • Better throughput and predictable failover
  • Does not support: message priority (pre-3.13), per-message TTL in early versions (now supported), exclusive queues, non-durable queues
# Declare a quorum queue via arguments
channel.queue_declare(queue='orders.q', durable=True, arguments={'x-queue-type': 'quorum'})

3.3 Streams (RabbitMQ 3.9+)

Append-only log abstraction (similar to Kafka topics). Supports:

  • Non-destructive reads (multiple independent consumers replaying from any offset)
  • High throughput for fan-out-heavy workloads
  • Long retention of messages
arguments={'x-queue-type': 'stream', 'x-max-length-bytes': 20_000_000_000}

Choose Streams when: you need replay, large fan-out, or event-sourcing-like semantics. Choose Quorum Queues when: you need traditional work-queue semantics with strong safety guarantees. Avoid Classic Queues for new systems unless you specifically need transient/exclusive queue semantics or per-message priority pre-3.13.

3.4 Exclusive & Auto-Delete Queues

  • Exclusive: used by only one connection, deleted when that connection closes. Useful for RPC reply queues or client-specific temp queues.
  • Auto-delete: deleted once the last consumer unsubscribes.

3.5 Queue Length Limits & Overflow Behavior

arguments={
  'x-max-length': 100000,
  'x-max-length-bytes': 500_000_000,
  'x-overflow': 'reject-publish'  # or 'drop-head' (default)
}

reject-publish is safer for critical data — it nacks new publishes instead of silently dropping the oldest messages.


4. Message Properties & Delivery Semantics

Key AMQP message properties to always set intentionally:

PropertyPurpose
delivery_mode2 = persistent (survives broker restart when queue is durable), 1 = transient
content_typee.g. application/json — helps consumers deserialize correctly
message_idUnique ID for dedup/tracing
correlation_idTies a request to a response (essential for RPC)
reply_toQueue name the responder should publish the answer to
timestampWhen the message was created
expirationPer-message TTL in ms (as a string)
headersArbitrary key-value metadata (tracing, versioning, routing)
app_id / typeUseful for debugging/observability

RabbitMQ’s delivery guarantee is “at-least-once” by default (with acks) — never “exactly-once” out of the box. Design consumers to be idempotent (see §17).


5. Publisher Confirms & Reliable Publishing

Without confirms, a publish can silently fail (network blip, broker crash before persisting) and you’d never know.

Publisher Confirms (confirm.select) make the broker asynchronously ack each published message once it’s safely handled (persisted to disk for durable queues, or routed for others).

channel.confirm_delivery()  # pika sync helper

# Async pattern (recommended for throughput):
channel.confirm_select()
outstanding = {}

def on_ack(frame):
    outstanding.pop(frame.delivery_tag, None)

def on_nack(frame):
    # republish or alert — broker couldn't handle the message
    handle_failed_publish(frame.delivery_tag)

channel.add_on_return_callback(on_returned)  # for mandatory=True unroutable messages

Best practice pattern:

  1. Enable confirms once per channel.
  2. Track outstanding (unconfirmed) messages in a map keyed by delivery_tag.
  3. Publish with mandatory=True if you need to detect unroutable messages (paired with a basic.return handler), or use an Alternate Exchange instead (more scalable than per-message mandatory flag under high throughput).
  4. Batch confirms for throughput — don’t wait for each message’s ack individually; publish many, then track acks asynchronously.
  5. On nack or timeout, retry with backoff or dead-letter to an “unconfirmed” audit queue.

Transactions vs Confirms

AMQP transactions (tx.select, tx.commit) exist but are much slower (synchronous round-trip per transaction) — publisher confirms are the modern, performant alternative and should almost always be preferred.


6. Consumer Acknowledgments & Prefetch (QoS)

6.1 Ack Modes

  • Manual ack (auto_ack=False, default recommended): consumer explicitly calls basic.ack after successfully processing. If the consumer dies before acking, the message is requeued and redelivered.
  • Auto ack (auto_ack=True): broker considers the message delivered the instant it’s sent over the wire — dangerous, messages are lost if the consumer crashes mid-processing. Only use for truly disposable/low-value data.
  • Nack/Reject: basic.nack(requeue=True/False) — explicitly signal failure. requeue=False typically routes to a DLX if configured, otherwise discards.

6.2 Prefetch (QoS)

basic.qos(prefetch_count=N) limits how many unacknowledged messages a consumer can hold at once. This is the single most important tuning knob for consumer throughput and fairness.

channel.basic_qos(prefetch_count=50)
  • Too low (e.g. 1): safe/fair but throughput-limited — the consumer idles waiting for the next message’s round trip.
  • Too high: one slow consumer can hoard thousands of messages while others starve; also risks large redelivery storms if that consumer crashes.
  • Rule of thumb: start with prefetch = (desired in-flight messages per consumer) based on processing time × target throughput. For fast, uniform tasks, 100–300 is common. For slow/heavy tasks (seconds each), keep it low (1–10) for fairness across consumers.
  • With multiple consumers on one channel, prefetch is shared across them unless set globally=False (per-consumer) — always use per-consumer QoS unless you explicitly want channel-wide sharing.

6.3 Manual Ack Ordering

Acks must be sent in order relative to delivery on a channel; you can batch-ack up to a given delivery tag with multiple=True — but be careful: this acks everything up to and including that tag, so never do this from concurrent workers sharing a channel without coordination.


7. Dead Letter Exchanges (DLX)

Messages become “dead” when:

  1. Rejected with requeue=False (basic.nack/basic.reject)
  2. TTL expires
  3. Queue length limit exceeded (overflow reject)

Configure a queue to route dead messages to a DLX:

channel.queue_declare(
    queue='orders.q',
    durable=True,
    arguments={
        'x-dead-letter-exchange': 'orders.dlx',
        'x-dead-letter-routing-key': 'orders.failed'
    }
)

Standard pattern — Retry with backoff using DLX + TTL:

orders.q  --(reject/expire)-->  orders.retry.dlx
                                       |
                                       v
                            orders.retry.q (TTL=5000ms, no consumers,
                                             DLX points back to orders.q)
                                       |
                        (after TTL expires, message dead-letters back)
                                       v
                                  orders.q  (retried)

This “parking lot queue” pattern implements delayed retry without needing a plugin. Attach a header-based retry counter (x-death array, automatically added by RabbitMQ) and inspect x-death[0].count to cap retries and route to a final poison-message / DLQ (dead-letter queue) for manual inspection after N attempts.

def handle_message(ch, method, properties, body):
    headers = properties.headers or {}
    deaths = headers.get('x-death', [])
    retry_count = deaths[0]['count'] if deaths else 0
    if retry_count >= 5:
        ch.basic_publish(exchange='orders.poison', routing_key='', body=body)
        ch.basic_ack(method.delivery_tag)
        return
    # ... process, nack with requeue=False to trigger retry via DLX

Always configure a DLX for production queues. A queue without a DLX silently loses rejected/expired messages.


8. TTL: Message & Queue

Per-message TTL:

properties = pika.BasicProperties(expiration='60000')  # 60 seconds, as a string in ms

Per-queue TTL (applies to all messages in the queue):

arguments={'x-message-ttl': 60000}

Queue TTL (auto-delete an idle queue):

arguments={'x-expires': 1800000}  # delete queue after 30 min of no use

Note: if both per-message and per-queue TTL are set, the lower value wins for each message. Also note that TTL expiry is evaluated lazily at the head of the queue in classic queues — a long-lived message stuck behind a not-yet-expired one won’t be dead-lettered until it reaches the head (classic queue behavior differs slightly from quorum/stream).


9. Delayed Messages

RabbitMQ has no native “delay X minutes then deliver” primitive out of the box. Two approaches:

9.1 Delayed Message Exchange Plugin (community plugin)

rabbitmq-plugins enable rabbitmq_delayed_message_exchange
channel.exchange_declare(
    exchange='delayed.exchange',
    exchange_type='x-delayed-message',
    arguments={'x-delayed-type': 'direct'}
)
properties = pika.BasicProperties(headers={'x-delay': 15000})  # 15s delay

Simple to use, but internally stores delayed messages in Mnesia which doesn’t scale well to very high volumes or very long delays — fine for moderate use.

9.2 TTL + DLX “Parking Lot” Pattern (native, more scalable)

Same mechanism as the retry pattern in §7: publish to a queue with x-message-ttl and no consumers, dead-lettering back to the real destination once the TTL expires. Preferred for high-scale or long-delay scenarios since it uses only core primitives.


10. Priority Queues

channel.queue_declare(queue='tasks.q', arguments={'x-max-priority': 10})
properties = pika.BasicProperties(priority=8)
  • Max priority levels 1–255 supported, but keep it small (e.g., 0–10) — each priority level adds internal overhead.
  • Only classic queues fully supported priority historically; quorum queues gained priority support in later versions — check your RabbitMQ version’s release notes before relying on this with quorum queues.
  • Priority only takes effect when there’s a backlog — if consumers keep up in real time, priority rarely matters since messages are consumed almost as fast as they arrive.

11. Clustering & High Availability

11.1 Cluster Basics

  • A RabbitMQ cluster shares users, vhosts, permissions, exchanges, and queue metadata across all nodes.
  • Queue contents (messages) live on specific node(s) unless using quorum queues/streams (which replicate data itself via Raft).
  • Nodes communicate via Erlang distribution — requires reliable low-latency network (same DC/AZ ideally, not WAN).
  • Replicate across N nodes using Raft; tolerates (N-1)/2 node failures.
  • A 3-node quorum queue survives 1 node failure; 5-node survives 2.
  • Automatic leader election on failure — clients transparently reconnect to the new leader.
  • Configure via policy or declare-time argument:
rabbitmqctl set_policy ha-orders "^orders\." '{"x-queue-type":"quorum"}' --apply-to queues

11.3 Classic Mirrored Queues (Legacy — avoid for new deployments)

Controlled via ha-mode policies (all, exactly, nodes). Known for split-brain/resync pain during network partitions; deprecated path — RabbitMQ team recommends quorum queues instead.

11.4 Load Balancing Clients

Put a TCP load balancer (HAProxy, cloud LB) or client-side node list in front of the cluster. Clients should implement reconnect-with-backoff logic since any node can fail.

11.5 Network Partitions

Configure cluster_partition_handling:

  • ignore (default, risky)
  • autoheal — cluster picks a winning partition and restarts nodes on the losing side
  • pause_minority — nodes in the minority partition pause themselves (safer for consistency, common in production)

11.6 Federation vs Clustering

Clustering = tight coupling, same logical broker, low-latency LAN. For cross-datacenter/WAN replication, use Federation or Shovel instead (§12) — never cluster across WAN links.


12. Federation & Shovel

12.1 Federation

Links exchanges/queues across separate brokers/clusters without merging them into one cluster. Good for:

  • Multi-datacenter event distribution
  • Aggregating events from edge clusters to a central cluster
rabbitmqctl set_parameter federation-upstream my-upstream \
  '{"uri":"amqp://user:pass@remote-broker","expires":3600000}'
rabbitmqctl set_policy federate-orders "^orders\." '{"federation-upstream-set":"all"}'

12.2 Shovel

A simpler, more explicit “pump” that moves messages from a source queue/exchange to a destination — runs as a plugin, good for one-off migrations or simple bridging between brokers, including different RabbitMQ versions.

rabbitmqctl set_parameter shovel my-shovel \
'{"src-uri":"amqp://source","src-queue":"orders.q",
  "dest-uri":"amqp://destination","dest-queue":"orders.q"}'

Federation vs Shovel: Federation is topology-aware (mirrors exchange bindings dynamically); Shovel is a fixed point-to-point pipe. Use Shovel for simple, well-defined bridges; Federation for more dynamic multi-cluster topologies.


13. Security

13.1 Users, Vhosts & Permissions

Always scope access with the principle of least privilege:

rabbitmqctl add_vhost orders-service
rabbitmqctl add_user orders_app StrongPassword!
rabbitmqctl set_permissions -p orders-service orders_app "^orders\." "^orders\." "^orders\."
# permissions: configure / write / read (regex patterns)

Never use the default guest user in production — it’s restricted to localhost connections by default for exactly this reason; don’t work around that restriction.

13.2 TLS

Enable TLS for all non-loopback traffic:

listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca.pem
ssl_options.certfile   = /path/to/cert.pem
ssl_options.keyfile    = /path/to/key.pem
ssl_options.verify     = verify_peer
ssl_options.fail_if_no_peer_cert = true

13.3 Authentication Backends

Beyond internal auth: LDAP, OAuth 2.0 (via rabbitmq_auth_backend_oauth2), x.509 client certs. Use OAuth2/LDAP for centralized identity management in larger orgs.

13.4 Runtime Hardening

  • Disable unused plugins and the management UI on production-facing nodes if not needed, or restrict access via firewall/VPN.
  • Rotate credentials; avoid embedding them in code — use secret managers.
  • Enable rabbitmq_shovel/federation credentials with scoped, dedicated users, not admin accounts.

14. Management, Monitoring & Observability

14.1 Management Plugin

rabbitmq-plugins enable rabbitmq_management

Provides an HTTP API and UI (default port 15672) for queues, connections, channels, exchanges, and policies.

14.2 Key Metrics to Alert On

  • Queue depth / message backlog growth rate — a growing queue means consumers can’t keep up.
  • Consumer utilisation (consumer_utilisation metric) — low value means consumers are often idle waiting, could indicate downstream bottlenecks or too-low prefetch.
  • Unacked message count — persistently high values suggest slow/stuck consumers or crashed processes not acking.
  • File descriptor / socket usage on the broker.
  • Memory & disk alarms (memory_high_watermark, disk_free_limit) — RabbitMQ blocks all publishers when these are triggered, so alert well before they fire.
  • Connection/channel churn — frequent reconnects indicate a client bug (e.g., opening a channel per message).

14.3 Prometheus Integration

rabbitmq-plugins enable rabbitmq_prometheus

Exposes metrics on port 15692 for Prometheus scraping — pair with Grafana dashboards (official RabbitMQ Grafana dashboards are published by the team).

14.4 Tracing

rabbitmq_tracing plugin logs message flow for debugging — only enable temporarily in dev/staging, it has real performance overhead.


15. Messaging Patterns

15.1 Work Queue (Task Distribution / Competing Consumers)

Multiple consumers bound to one queue; RabbitMQ round-robins deliveries (subject to prefetch). Use for parallelizable, independent jobs (image resizing, email sending).

15.2 Publish/Subscribe

Fanout exchange → multiple queues, one per subscriber service. Each service gets its own copy of every event.

15.3 Routing (Direct Exchange)

Selective delivery based on exact routing key match — e.g., severity-based log routing (error, warning, info to different queues).

15.4 Topics

Multi-criteria routing using wildcard patterns — e.g., <region>.<service>.<event_type>.

15.5 RPC (Request/Reply)

# Requester
result = channel.queue_declare(queue='', exclusive=True)  # anonymous callback queue
callback_queue = result.method.queue

channel.basic_publish(
    exchange='', routing_key='rpc_queue',
    properties=pika.BasicProperties(reply_to=callback_queue, correlation_id=corr_id),
    body=request
)
# consume from callback_queue, match correlation_id, then get response

Caution: RPC over AMQP couples services synchronously and adds latency/complexity. Prefer async event-driven flows where possible; reserve RPC for genuinely synchronous needs.

15.6 Saga Pattern (Distributed Transactions)

Break a distributed transaction into a sequence of local transactions, each publishing an event that triggers the next step. On failure, publish compensating events to undo prior steps.

OrderCreated → PaymentReserved → InventoryReserved → OrderConfirmed
                     |                    |
              (failure)            (failure)
                     v                    v
           PaymentReleased ←──── InventoryReleaseFailed

Use a choreography (event-driven, no central coordinator) approach for simple flows, or orchestration (a dedicated saga coordinator service) for complex multi-step flows needing visibility/control.

15.7 Transactional Outbox

To avoid the “dual write” problem (DB write + message publish not being atomic), write the event to an outbox table in the same DB transaction as the business change, then use a separate relay process (polling or CDC via Debezium) to publish to RabbitMQ and mark the outbox row as sent. Guarantees at-least-once delivery consistent with the DB state.

15.8 Scatter-Gather

Publish a request to multiple services (fanout) each replying to a shared correlation-tracked reply queue; aggregate responses with a timeout — useful for parallel enrichment/lookup fan-out.

15.9 Circuit Breaker on Consumers

Wrap downstream calls (DB/HTTP) inside consumers with a circuit breaker; on repeated failure, stop consuming (cancel/pause) rather than nacking messages into an infinite requeue loop that hammers a struggling dependency.


16. Connection & Channel Management Best Practices

  • One long-lived connection per process/service, not per message. Connections are expensive (TCP + AMQP handshake + heartbeats).
  • One channel per thread/coroutine — channels are not thread-safe; never share a channel across concurrent threads without external locking.
  • Never open a channel per message — this is the #1 cause of RabbitMQ performance complaints. Reuse channels.
  • Use a connection pool in high-concurrency environments, sized to your concurrency level, not per-request.
  • Enable heartbeats (default 60s) so dead TCP connections are detected promptly rather than hanging silently.
  • Implement automatic reconnect with exponential backoff + jitter — most client libraries (Spring AMQP, amqplib with reconnect wrappers, aio-pika) support this natively or via small wrapper code.
  • Handle channel.close events (e.g., due to a protocol error like publishing to a non-existent exchange) by recreating the channel — a closed channel is permanently unusable.

17. Idempotency & Error Handling

RabbitMQ guarantees at-least-once delivery under normal ack-based operation — duplicates will happen (e.g., ack lost after processing but before broker receives it, consumer crash after processing but before ack, network retries). Design for this:

  • Use a unique message ID (message_id or a business key) and track processed IDs in your database/cache (INSERT ... ON CONFLICT DO NOTHING, Redis SETNX, etc.) before applying side effects.
  • Make business logic operations idempotent where feasible (e.g., “set status to X” rather than “increment counter”).
  • On processing failure: distinguish transient errors (network blip, DB timeout — safe to nack(requeue=True) or retry via DLX/backoff) from permanent errors (malformed payload, business rule violation — route straight to a poison-message queue, don’t infinite-loop retry).
  • Always cap retries (see §7’s x-death count pattern) to avoid infinite requeue loops burning CPU/network.
  • Log correlation IDs / message IDs at every hop for traceability across services.

18. Performance Tuning

  • Batch acknowledgments where safe (multiple=True) to reduce protocol chatter — but understand the “ack everything up to X” semantics before doing this concurrently.
  • Increase prefetch for lightweight/fast tasks; keep it low for heavy/slow tasks (see §6.2).
  • Use persistent messages only when necessary — durability costs disk I/O; transient messages in non-durable queues are much faster for use cases where loss is acceptable (metrics, ephemeral events).
  • Avoid huge messages (>128KB is a common soft guideline) — for large payloads, publish a reference (e.g., S3 URL) instead of the blob itself.
  • Lazy queues / paging: quorum queues and modern classic queues automatically page messages to disk when memory pressure is high — but if you see high queue depth with heavy consumer contention, check disk I/O since paging kicks in.
  • Right-size your cluster: quorum queues shine with 3–5 nodes but too many replicas per queue increases Raft overhead — 3 replicas is often the sweet spot even for large clusters (place queue leaders across nodes evenly).
  • Avoid too many queues/exchanges on one node — thousands of queues each with their own Erlang process add scheduling overhead; consider queue sharding or consolidating with routing keys instead of one queue per entity.
  • Monitor consumer_utilisation to right-size consumer concurrency instead of guessing.
  • Use multiple cores: run enough consumer processes/threads to saturate available cores — a single-threaded consumer with high prefetch won’t use concurrency it doesn’t have.

19. Common Pitfalls

PitfallWhy it hurtsFix
Opening a channel per messageMassive overhead, connection churnReuse long-lived channels
auto_ack=True everywhereSilent message loss on crashManual ack with proper error handling
No DLX configuredRejected/expired messages vanish silentlyAlways attach a DLX to production queues
Unbounded prefetch (0/very high)One consumer hoards messages, unfair distribution, huge redelivery storms on crashTune prefetch deliberately
Publishing without confirms on critical dataSilent data loss on broker/network failureEnable publisher confirms
Treating RabbitMQ as exactly-onceIt’s at-least-once by designBuild idempotent consumers
Classic mirrored queues for new HA needsDeprecated, split-brain proneUse quorum queues
Clustering across WANHigh latency breaks Erlang distribution, partitionsUse Federation/Shovel across WAN instead
No queue length/TTL limitsUnbounded memory/disk growth from a stuck consumerSet x-max-length, TTL, x-overflow
Using default guest user in prodSecurity riskDedicated least-privilege users + TLS
Ignoring x-death retry countInfinite retry loops on poison messagesCap retries, route to poison queue

20. Quick Reference Cheat Sheet

# Durable, quorum queue with DLX and TTL-based retry
channel.queue_declare(
    queue='orders.q',
    durable=True,
    arguments={
        'x-queue-type': 'quorum',
        'x-dead-letter-exchange': 'orders.dlx',
        'x-dead-letter-routing-key': 'orders.retry'
    }
)

# Publisher: confirms + persistence
channel.confirm_select()
channel.basic_publish(
    exchange='orders.direct',
    routing_key='order.created',
    body=payload,
    properties=pika.BasicProperties(
        delivery_mode=2,
        content_type='application/json',
        message_id=str(uuid4()),
        correlation_id=corr_id
    ),
    mandatory=True
)

# Consumer: manual ack + sane prefetch
channel.basic_qos(prefetch_count=50)
channel.basic_consume(queue='orders.q', on_message_callback=handle, auto_ack=False)

Essential rabbitmqctl commands:

rabbitmqctl list_queues name messages consumers memory
rabbitmqctl list_connections
rabbitmqctl list_channels
rabbitmqctl cluster_status
rabbitmqctl set_policy <name> <pattern> '<definition>' --apply-to queues
rabbitmqctl node_health_check

Further Reading

This note is part of the Digital Garden — a collection of connected, evolving thoughts.