From 4df9bcdd5b42546f2f4470c0a8182a9ae4da5b81 Mon Sep 17 00:00:00 2001 From: nook24 Date: Fri, 21 Aug 2026 19:55:59 +0200 Subject: [PATCH] Declare RabbitMQ queues and exchange durable by default A queue that is neither durable nor exclusive is RabbitMQ's deprecated transient_nonexcl_queues feature. That is no longer only a deprecation: RabbitMQ 4 reports it as denied_by_default and refuses every queue.declare with a connection exception. Because Connect() returns false as soon as a declare fails, RabbitMQ does not merely lose those queues - it fails to connect at all, and publishes nothing. Verified against RabbitMQ 4.3.5: rabbitmqctl list_deprecated_features transient_nonexcl_queues | denied_by_default | denied Durable queues have worked since AMQP 0-9-1, so this is the only setting that works on every RabbitMQ version, from 3.x through 4.x. This does not put monitoring events on disk. Queue durability and message persistence are separate AMQP properties: durable stores the queue *definition*, while a message is only written durably when its publisher marks it persistent. SendMessage passes properties=nullptr, so every message this module publishes is transient and stays that way. The RabbitMQ documentation is explicit that transient messages "will be discarded during recovery, even if they were stored in durable queues". So the behaviour Statusengine wants is unchanged: the queues buffer in RAM while no worker is connected, and a RabbitMQ restart empties them. Measured rather than assumed - 5 messages in a durable queue, RabbitMQ restart, queue present with 0 messages. Publishing 20,000 events took 0.22-0.23s with durable queues and 0.22-0.23s without, three runs each. The exchange follows the queues. A transient exchange loses its bindings on a RabbitMQ restart while the durable queues survive, so the pair is kept consistent; both are metadata only and neither costs per-message I/O. Both values remain configurable, so an installation that needs the old behaviour can still set DurableQueues = false - on a RabbitMQ old enough to accept it. Tested with Naemon 1.4.1 against RabbitMQ 3.9.27, publishing to all configured queues, alongside the Go worker consuming them. Because both sides declare the same queues and AMQP answers a mismatched redeclare with a 406 PRECONDITION_FAILED rather than reconciling it, this change belongs with the matching one in Statusengine Go Worker; both start orders were checked, module first and worker first, with no 406 either way. Note for existing installations: the queues already exist as non-durable and cannot be redeclared. They have to be deleted once, with the monitoring core and the worker stopped. Messages waiting in them are lost, which is acceptable for the same reason the design is - they are transient and would not have survived a broker restart either. --- src/Configuration.h | 19 +++++++++++++++++-- statusengine.toml | 8 ++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Configuration.h b/src/Configuration.h index ee072a9..673601d 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -132,8 +132,23 @@ namespace statusengine { Exchange = GetTomlDefault<>(tbl, "Exchange", std::string("statusengine")); - DurableExchange = GetTomlDefault<>(tbl, "DurableExchange", false); - DurableQueues = GetTomlDefault<>(tbl, "DurableQueues", false); + // Durable by default. A queue that is neither durable nor exclusive is + // RabbitMQ's deprecated transient_nonexcl_queues feature: 3.13 warns once per + // broker start, 4.x refuses the declare outright, and the broker then fails to + // connect at all (see Connect()). Durable has worked since AMQP 0-9-1, so it is + // the only value that works on every supported broker version. + // + // This does not put events on disk. Queue durability and message persistence + // are separate: durable stores the queue *definition*, while messages are only + // written durably when the publisher marks them persistent - and SendMessage + // passes properties=nullptr, i.e. transient. So the queues still buffer in RAM + // and a broker restart still empties them, which is the intended behaviour. + // + // The exchange follows the queues: a transient exchange loses its bindings on a + // broker restart while the durable queues survive, and keeping the pair + // consistent costs nothing, both being metadata only. + DurableExchange = GetTomlDefault<>(tbl, "DurableExchange", true); + DurableQueues = GetTomlDefault<>(tbl, "DurableQueues", true); SSL = GetTomlDefault<>(tbl, "SSL", false); diff --git a/statusengine.toml b/statusengine.toml index 7139368..0bf2b5a 100644 --- a/statusengine.toml +++ b/statusengine.toml @@ -45,8 +45,12 @@ WorkerCommand = "statusngin_cmd" ##Vhost = "/" ##Timeout = 30 ##Exchange = "statusengine" -##DurableExchange = false -##DurableQueues = false +## Durable queues and exchange. Required by RabbitMQ 4, which refuses to declare a queue +## that is neither durable nor exclusive. This stores queue and exchange *definitions* on +## disk, not the events: messages are published transient, so the queues still buffer in +## RAM and are still emptied by a broker restart. +##DurableExchange = true +##DurableQueues = true ##SSL = false ##SSL_verify = true ##SSL_cacert = ""