Skip to content

Queue Arguments#

Beyond durable, exclusive and auto_delete, RabbitMQ configures a queue through optional arguments: a dictionary of x-* keys sent with the declaration. They control message TTL, queue length, overflow behavior, dead-lettering, priorities and more. The full list lives in the RabbitMQ documentation.

RabbitQueue forwards them through the arguments parameter:

from faststream.rabbit import RabbitQueue

queue = RabbitQueue(
    "orders",
    arguments={
        "x-message-ttl": 60_000,
        "x-max-length": 10_000,
        "x-overflow": "reject-publish",
    },
)

x-queue-type is set for you from the queue_type parameter, so don't put it into arguments. The accepted keys depend on that type, and RabbitQueue is typed accordingly: your type checker flags a key the chosen queue type doesn't support, and your editor can complete the rest.

Common Arguments#

Argument Queue types Meaning
x-message-ttl classic, quorum Milliseconds a message may wait in the queue before it expires.
x-expires classic, quorum Milliseconds of no use (no consumers, no declarations) after which the queue is deleted.
x-max-length classic, quorum Maximum number of ready messages.
x-max-length-bytes all Maximum total body size of ready messages.
x-overflow classic, quorum What to do at the limit: drop-head (default) or reject-publish; classic queues also accept reject-publish-dlx.
x-single-active-consumer classic, quorum Deliver to one consumer at a time, failing over to the next one.
x-dead-letter-exchange classic, quorum Exchange receiving rejected and expired messages. See below.
x-dead-letter-routing-key classic, quorum Routing key to dead-letter with. Keeps the original key when unset.
x-max-priority classic Enables message priorities up to this value.
x-delivery-limit quorum Redeliveries after which a message is dropped or dead-lettered.
x-max-age stream Retention of a stream, for example "7D".

Arguments that belong to the consumer rather than the queue (x-stream-offset, x-priority) go to the subscriber's consume_args instead, as the stream example shows.

Queue arguments are immutable

RabbitMQ refuses to redeclare an existing queue with different arguments and fails the declaration with PRECONDITION_FAILED. To change the arguments of a queue that already exists on the server, delete that queue first or pick a new name.

Dead Letter Queue#

When a consumer rejects a message, or a message sits in a queue longer than its TTL, RabbitMQ can forward it to a dead letter exchange instead of dropping it. A queue bound to that exchange collects those messages, so you can inspect, alert on, or replay them later.

The dead-lettering rules live on the source queue as the x-dead-letter-exchange and x-dead-letter-routing-key arguments. The dead letter exchange and the queue bound to it are ordinary objects that you declare like any other:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from faststream import FastStream, Logger
from faststream.exceptions import RejectMessage
from faststream.rabbit import (
    ExchangeType,
    RabbitBroker,
    RabbitExchange,
    RabbitMessage,
    RabbitQueue,
)

broker = RabbitBroker()
app = FastStream(broker)

dead_letter_exchange = RabbitExchange("orders-dlx", type=ExchangeType.DIRECT)
dead_letter_queue = RabbitQueue("orders-dead", routing_key="orders")

orders_queue = RabbitQueue(
    "orders",
    arguments={
        "x-dead-letter-exchange": "orders-dlx",
        "x-dead-letter-routing-key": "orders",
        "x-message-ttl": 60_000,
    },
)


@broker.subscriber(dead_letter_queue, dead_letter_exchange)
async def handle_dead_letter(
    order_id: str,
    msg: RabbitMessage,
    logger: Logger,
) -> None:
    reason = msg.headers["x-death"][0]["reason"]
    logger.warning("order %s dead-lettered: %s", order_id, reason)


@broker.subscriber(orders_queue)
async def handle_order(order_id: str, logger: Logger) -> None:
    if order_id.startswith("bad"):
        raise RejectMessage

    logger.info("processed %s", order_id)

A rejected order goes to orders-dlx with the routing key orders, lands in orders-dead and reaches handle_dead_letter as the very same message, plus an x-death header that says why it was dead-lettered (rejected, expired, maxlen or delivery_limit) and from which queue. Subscribers start in the order they are declared, each one declaring its queue and exchange and consuming right away, so declare the dead letter subscriber first: orders-dlx and orders-dead then exist before the first order can be rejected.

The same path is taken by any message that expires after x-message-ttl milliseconds, and by messages a handler fails on with an unhandled exception, since the default acknowledgement policy rejects those too.

Use a dead letter queue whenever a handler can fail for a reason a retry won't fix (a malformed payload, a business rule violation): requeuing such a message only makes the same handler fail again. For transient failures, use a quorum queue with x-delivery-limit and ack_policy=AckPolicy.NACK_ON_ERROR: a failed message is requeued and redelivered that many times first, and dead-lettered only after that. Under the default REJECT_ON_ERROR policy a failure rejects the message without requeue, so it is dead-lettered right away.

Tip

Without x-dead-letter-routing-key, a message is dead-lettered with the routing key it was originally published with. That is handy when one dead letter exchange serves several queues: bind one dead letter queue per source routing key, or use a fanout exchange to collect everything in one place.