BROKABROKA
Sign inDownload CommunityRequest a demo
GuideRabbitMQ

Reading a routing problem from the bindings out

When a published message does not arrive, it was usually never in a queue. It matched no binding and was discarded at the exchange — unless an alternate exchange caught it. Read the exchange and its bindings before you read the queue, and read a publish result for what it says: the broker took it, returned it, or refused it — never that a consumer received it.

8 min read

A producer says it published. A consumer says nothing came. The instinct is to open the queue, and the queue will show you nothing, because in RabbitMQ a message that matches no binding is not lost from a queue — it never reached one. The broker discarded it at the exchange, silently, the moment the routing key failed to match. This is the order that finds where the match broke.

1 — Start at the exchange: arriving, and leaving?

The Exchanges list shows each exchange with its type, its flags, its alternate exchange, and the rate of messages arriving at it and leaving it. If messages are arriving and none are being routed onward, the pair is marked: they match no binding. That is the shape of a misconfigured routing key or a binding somebody removed, and it is visible here before anyone notices the queue that stopped filling. The RabbitMQ Overview carries the same signal for the whole cluster: an Unroutable rate, counting both the messages discarded silently and those returned to a publisher that asked for them.

The Exchanges list for one vhost: twelve exchanges with type, flags — durable, auto-delete, internal — alternate exchange and an in/out rate pair, which only orders reports: 34.0 messages a second in and 62.0 out.
The in/out pair is the first thing to read. Here orders is routing what it receives; an exchange receiving and routing nothing is discarding it, and the queue behind it will look empty for a reason that is not in the queue.

Two things on this list change the diagnosis. An internal exchange cannot be published to by a client at all — only other exchanges may route into it — so a producer aimed at one is refused by the broker, not misrouted; the console's own Publish dialog says so on such an exchange and will not send. And the type is shown as the broker reported it, plugin types included, because the type decides what the routing key even means.

2 — Draw the bindings

The Routing screen lays exchanges and queues out left to right, one curve per binding with the routing key written on it. Horizontal distance is hop count: every arrow runs left to right, and an object sits one column right of its furthest predecessor. An exchange-to-exchange hop is a shape rather than a detail to reconstruct — a message entering on the left and arriving two columns over has passed through something on the way.

The Routing screen for one vhost with Publish through set to orders: the orders topic exchange on the left, curves labelled order.#, order.created and order.# running to the queues orders.audit and orders.processing and to the fanout exchange orders.audit.fanout, whose unlabelled arrow carries on to orders.audit.archive a column further right — 4 bindings across 5 objects.
Publishing through orders reaches three queues, one of them two columns over: that message passed through a fanout exchange on the way, and the fanout's arrow carries no label.

Three controls narrow it. Publish through takes one exchange and keeps everything reachable from it, following exchange-to-exchange hops — the answer to "if I publish here, where can it end up". Include the default exchange is off by default: the broker binds every queue to the default exchange automatically under the queue's own name, and drawing those hides the routing somebody configured. Include built-in exchanges is off as well: it leaves out the amq.* exchanges the broker creates and the internal ones a federation link or a shovel declares for itself — though an amq.* exchange somebody bound a queue to is drawn anyway. Routing is drawn per virtual host and the screen refuses to combine them, because two vhosts can each hold an exchange called orders with nothing in common.

3 — Check the key against the type

Follow the producer's routing key along the curves. What counts as a match depends on the exchange it enters. On a direct exchange the key has to equal the binding key. On a topic exchange the binding key is a pattern: * matches one word and # matches zero or more. A fanout exchange ignores the key entirely, so its arrows carry no label. A headers exchange ignores it too and matches on arguments instead, which is why those arrows read headers rather than showing an empty key.

A key that is one word off — order.created published against a binding of orders.# — matches nothing, and nothing on the producer side reports it.

When the binding you need is missing, add it where the routing starts: Bindings in the exchange's row menu, then New binding. Choose a queue or another exchange as the destination — a stream is bound as a queue — and give the routing key; the hint beneath it says how this exchange's type reads the key. On a headers exchange you also choose x-match and give the arguments, and a headers binding left with nothing to match on routes every message, which the dialog warns about before you create it. Nothing can be bound to the default exchange, and the dialog says so.

Bind to orders over the Exchanges list: the destination a queue named orders.audit, with the note that a stream is a queue and is bound as one; the routing key order.refunded, with the hint that * matches exactly one word and # zero or more; empty arguments; and Cancel beside Bind.
The hint under the key belongs to this exchange: orders is a topic exchange, so the key is a pattern.

4 — Unroutable is not dead-lettered

This is where an investigation goes to the wrong queue. Two different things happen to a message nobody wanted.

A message that matches no binding was never in a queue. It is discarded at the exchange — unless that exchange has an alternate exchange, which catches it. The Exchanges list shows the alternate whether it was declared on the exchange or set by a policy, and says which on hover. A message that is dead-lettered was in a queue and left it: rejected, expired, or dropped because the queue was full or a delivery limit was reached. It goes to the queue's dead-letter exchange.

Different mechanism, different configuration, different place to look. A dead-letter queue with nothing in it tells you nothing about a routing failure, because a routing failure never produces a dead letter. And the alternate exchange has a trap of its own: the broker accepts the name without checking that the exchange exists. If it does not, unroutable messages are still discarded — the exact outcome the setting was added to prevent, arrived at silently.

5 — Publish, and read the answer for what it is

The test is a publish, and the broker answers it. BROKA publishes over AMQP with publisher confirms and the mandatory flag, so the answer is one of three: Confirmed — at least one queue took the message; Returned — no binding matched it and no alternate exchange took it, shown with the broker's reply code and text; or Refused (nacked) — a queue it reached would not take it, such as one at its length limit that rejects publishes. A message an alternate exchange caught is confirmed, because a queue took it there. None of the three says a consumer received the message.

Where you publish from decides what you are testing. Publishing to an exchange exercises your routing. Publishing from a queue's own page goes through the default exchange straight to that queue, which skips the exchange and the bindings your applications publish through — it exercises the consumer instead. The routing-key field explains itself per type: on the default exchange the key is a queue name; a fanout ignores it; a headers exchange matches on headers.

To test a headers exchange, give the message its headers in the same dialog, each with a type: a binding on 3 is not matched by the string "3", and the dialog says so beside the rows. The properties sit below them — persistence, and a content type that starts as text/plain and that the broker does not check.

6 — Only then, the queue

A queue's own page answers the other direction — where its messages come from — listing each source exchange and its routing key, with three renderings because an empty key means three different things: the key itself, matched on headers where the binding carries arguments, and (no routing key) where it genuinely has none.

If you want to confirm the test message arrived, read the ready count. Reading the messages themselves is not a peek: the requeueing modes put them back at the head of the queue marked redelivered, and the other two remove them.

What is recorded, and what the environment allows

Publishing through the console is a write. It is refused in a read-only environment, for administrators too, and on a connection frozen with its own read-only switch, and the refusal is recorded. Removing a binding is a delete: it needs a Reason before it runs, in every environment — a guarded environment asks for one on a publish as well — and its audit entry ends with the consequence rather than the parameters: messages matching it are no longer routed there. Adding a binding is a write like a publish. Deleting an exchange asks for a reason too, and its confirmation carries Delete only if unused, ticked when it opens, so the broker itself refuses while the exchange is still the source of a binding; clearing it is a deliberate act, recorded as such. The default exchange and the amq.* exchanges cannot be deleted, and their Delete item says why. The RabbitMQ page describes the rest of the surface these screens belong to.

What this article does not cover

  • A dry run. There is no routing-key tester; the publish is the test, and its answer is confirmed, returned or refused.
  • Policies. A policy or an operator policy can set a queue's dead-letter target, and the queue's page says which one the broker uses. Which policy wins is its own subject.
  • Delivery. Nothing on this path says a consumer received a message. Confirmed means a queue took it; what happened after is on the consumer's side.

Try it yourself

The rabbitmq-routing lab starts RabbitMQ with the exchange graph from this article, including an exchange-to-exchange hop, an alternate exchange and an exchange that routes nowhere, and keeps publishing so the unroutable path stays visible.

Applies to BROKA 1.0 · RabbitMQ · Commercial.