3.3 KiB
UNDF: UNDF-2026-000000473
rmq-0005: rabbit_mgmt_wm_definitions export_binding O(B×Q) → O(B+Q)
Location
deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
Lines 54–58 (all_definitions/2) and 115–120 (vhost_definitions/2)
Severity
MEDIUM — triggered by GET /api/definitions (HTTP API export). Automation tools,
monitoring pipelines, and GitOps workflows hit this endpoint on a schedule. In large
deployments it serialises the entire broker definition and becomes a CPU hotspot.
Description
all_definitions/2 builds QNames as a plain list of {Name, VHost} tuples, then
calls export_binding(B, QNames) for every binding B in a list comprehension.
Inside export_binding/2, lists:member({Dest, VHost}, QNames) performs a linear
scan of all queue-name tuples for every binding processed.
With B bindings and Q queues this is O(B × Q). In a busy broker (e.g., a topic exchange with 10 k routing keys matched against 1 k queues) the export scans 10 M tuple comparisons per API call. If called every 15 seconds by a monitoring tool, this is 666 k redundant comparisons/second sustained.
Root Cause
%% all_definitions/2 — deps/rabbitmq_management/src/rabbit_mgmt_wm_definitions.erl
Qs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData), export_queue(Q)],
QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs], %% ← plain list
Bs = [B || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
export_binding(B, QNames)], %% ← O(B) calls
%% export_binding/2 — same file
export_binding(Binding, Qs) ->
...
( (DestType =:= queue andalso lists:member({Dest, VHost}, Qs)) %% ← O(Q) scan
...
vhost_definitions/2 has the identical pattern at lines 115–120 using QNames.
Fix
Convert QNames to an ordsets:ordset() (or sets:set() with {version, 2}) before
the binding comprehension. Pass the set to export_binding/2 and use
sets:is_element/2 in place of lists:member/2.
%% all_definitions/2 — fixed
Qs = [Q || Q <- rabbit_mgmt_wm_queues:basic(ReqData), export_queue(Q)],
QNames = [{pget(name, Q), pget(vhost, Q)} || Q <- Qs],
QNamesS = sets:from_list(QNames, [{version, 2}]), %% ← O(Q log Q) once
Bs = [B || B <- rabbit_mgmt_wm_bindings:basic(ReqData),
export_binding(B, QNamesS)], %% ← O(B) calls
%% export_binding/2 — fixed
export_binding(Binding, Qs) ->
...
( (DestType =:= queue andalso sets:is_element({Dest, VHost}, Qs)) %% ← O(1)
...
The same change applies to vhost_definitions/2.
Complexity
| Before | After | |
|---|---|---|
| Build QNames | O(Q) | O(Q) + O(Q log Q) set build |
| Per-binding check | O(Q) scan | O(1) set lookup |
| Total export | O(B × Q) | O(Q log Q + B) |
At B=10 k bindings, Q=1 k queues: 10 M ops → 14 k ops (714x improvement). At B=100 k bindings, Q=10 k queues: 10^9 ops → 133 k ops (7500x improvement).
Context
GET /api/definitions is the standard RabbitMQ backup/export endpoint. It is called:
- By
rabbitmqctl export_definitions - By monitoring pipelines (Datadog, Prometheus exporters)
- By GitOps automation to detect config drift
Slow exports block the HTTP listener and increase memory pressure from large response bodies held in flight while CPU-bound scan is underway.