Alerting on Flow Control & Receive-Queue Depth
This guide extends the alerting design in Python Monitoring & Alerting Patterns for Galera and answers a single operational question: which wsrep_ metrics warn you that a MariaDB Galera cluster is about to stall, and where do you set the thresholds? A synchronous group throttles its own write throughput to protect consistency, so the earliest sign of trouble is not an error — it is a rising apply backlog and a growing fraction of time spent paused. Alert on wsrep_flow_control_paused, wsrep_local_recv_queue_avg, and wsrep_flow_control_sent correctly and you get minutes of warning before commit latency spikes cluster-wide; alert on them naively and you drown in false pages during every batch job. This page gives the exact thresholds, the PromQL and Python alert rules, and the causal reasoning that ties each metric to an imminent stall.
Context: Why These Metrics Predict a Stall
Galera commits at the speed of its slowest member because every write-set must be received and eventually applied on every node. When one node applies slower than the group produces write-sets, its received-but-not-yet-applied queue grows. wsrep_local_recv_queue_avg is the average depth of that queue since the metric was last read — it is the leading indicator, rising before any throttling happens. Once the queue crosses the provider’s gcs.fc_limit, that node sends a flow-control pause message to the whole group, incrementing its wsrep_flow_control_sent counter and forcing every other member to stop accepting new writes until the backlog drains. wsrep_flow_control_paused is the fraction of wall-clock time the node spent in that paused state — the lagging indicator that the stall is already happening.
Read as a chain, the three metrics form an early-warning sequence: recv-queue climbs → this node sends flow control → the whole cluster pauses. That is why alerting on the queue depth buys you lead time that alerting on paused-time alone does not. The throttle itself — the gcs.fc_limit and gcs.fc_factor hysteresis these metrics measure — is tuned in Tuning Flow Control to Prevent Cluster Stalls, and the certification pipeline that feeds the apply queue is explained in Write-Set Certification Process Explained.
Solution: The Alert Rules
The correct approach differs by metric type. wsrep_flow_control_paused is an interval average reset on read, so in Prometheus you should alert on the rate of the monotonic wsrep_flow_control_paused_ns counter instead, which is scrape-timing-independent. wsrep_flow_control_sent is a monotonic counter, so alert on its rate(). wsrep_local_recv_queue_avg is a gauge you can threshold directly, but require it to hold across a for: window so a single burst does not page.
PromQL alert rules
These consume the gauges from Building a Prometheus Exporter for wsrep Status:
groups:
- name: galera_flow_control
rules:
# Leading indicator: apply backlog building on a member.
- alert: GaleraRecvQueueHigh
expr: galera_wsrep_local_recv_queue_avg > 20
for: 2m
labels: {severity: warning}
annotations:
summary: "{{ $labels.node }} apply backlog rising (recv_queue_avg {{ $value | printf \"%.1f\" }})"
# Lagging indicator: paused fraction from the monotonic ns counter.
- alert: GaleraFlowControlPaused
expr: rate(galera_wsrep_flow_control_paused_ns[5m]) / 1e9 > 0.10
for: 3m
labels: {severity: warning}
annotations:
summary: "Cluster throttled: {{ $labels.node }} paused fraction {{ $value | printf \"%.2f\" }}"
- alert: GaleraFlowControlStalled
expr: rate(galera_wsrep_flow_control_paused_ns[5m]) / 1e9 > 0.25
for: 2m
labels: {severity: critical}
annotations:
summary: "Cluster stalling: {{ $labels.node }} paused over 25% of wall-clock time"
# Trigger: identify which member is the flow-control culprit.
- alert: GaleraFlowControlSender
expr: rate(galera_wsrep_flow_control_sent[5m]) > 0.1
for: 5m
labels: {severity: warning}
annotations:
summary: "{{ $labels.node }} is sending flow-control pauses to the group"
rate(...paused_ns[5m]) / 1e9 yields the paused fraction over the window: a value of 0.10 means the node was throttled 10% of the time. The GaleraFlowControlSender rule is the one that names the culprit — the member with a non-zero wsrep_flow_control_sent rate is the node whose apply pipeline is holding the group back, which is where you look first.
Python threshold alerting
If you alert from the Python collector rather than Prometheus, the same thresholds apply, but you must compute the paused fraction yourself from successive wsrep_flow_control_paused_ns samples. The evaluator handles the Galera contention codes 1213 (deadlock / certification conflict) and 1205 (lock wait timeout) so a busy node is not misread as unreachable:
"""Flow-control alerting from the Python collector (PyMySQL, 3.9+)."""
import time
import pymysql
WARN_PAUSED, CRIT_PAUSED = 0.10, 0.25
WARN_QUEUE, CRIT_QUEUE = 20.0, 100.0
def _read(host, user, pw):
conn = pymysql.connect(host=host, user=user, password=pw,
connect_timeout=4, read_timeout=4)
try:
with conn.cursor() as cur:
cur.execute("SHOW GLOBAL STATUS WHERE Variable_name IN "
"('wsrep_flow_control_paused_ns','wsrep_local_recv_queue_avg',"
"'wsrep_flow_control_sent')")
return {n: v for n, v in cur.fetchall()}
except pymysql.err.OperationalError as exc:
if exc.args and exc.args[0] in (1213, 1205):
return None # transient contention, skip this sample
raise
finally:
conn.close()
def check_flow_control(host, user, pw, interval=15):
"""Sample twice to derive the paused fraction from the ns counter."""
first = _read(host, user, pw)
if first is None:
return
time.sleep(interval)
second = _read(host, user, pw)
if second is None:
return
paused_delta_ns = int(second["wsrep_flow_control_paused_ns"]) - \
int(first["wsrep_flow_control_paused_ns"])
paused_frac = paused_delta_ns / (interval * 1e9) # ns paused / ns elapsed
queue = float(second["wsrep_local_recv_queue_avg"])
if paused_frac >= CRIT_PAUSED:
yield "critical", f"{host} paused {paused_frac:.0%} of the interval"
elif paused_frac >= WARN_PAUSED:
yield "warning", f"{host} paused {paused_frac:.0%} of the interval"
if queue >= CRIT_QUEUE:
yield "critical", f"{host} recv_queue_avg={queue:.0f} (apply backlog)"
elif queue >= WARN_QUEUE:
yield "warning", f"{host} recv_queue_avg={queue:.0f} (apply backlog)"
Deriving the fraction from the delta of the nanosecond counter over a known elapsed interval avoids the reset-on-read trap that makes wsrep_flow_control_paused unreliable when more than one tool reads status.
Parameter & Threshold Reference
| Metric | Type | Healthy | Warn | Critical | Meaning of a breach |
|---|---|---|---|---|---|
wsrep_local_recv_queue_avg |
gauge (interval avg) | < 5 |
≥ 20 |
≥ 100 |
Received write-sets queuing faster than this node applies them |
wsrep_flow_control_paused |
float 0–1 (reset on read) | ~0.0 |
≥ 0.10 |
≥ 0.25 |
Fraction of the interval the group was throttled; use the ns counter in Prometheus |
wsrep_flow_control_paused_ns |
counter (nanoseconds) | flat | rising | rising fast | Cumulative pause time; rate()/1e9 gives the paused fraction |
wsrep_flow_control_sent |
counter | flat | any sustained rate | rising fast | This node is the one requesting pauses — the group’s bottleneck |
wsrep_flow_control_recv |
counter | flat | — | — | Pause requests received; high everywhere but low _sent means the culprit is elsewhere |
wsrep_local_recv_queue |
gauge (instant) | < 5 |
≥ 40 |
≥ 200 |
Instantaneous backlog; noisier than the average, use for spot checks |
The gcs.fc_limit provider option is the queue depth at which a node actually sends flow control, so tune your recv_queue_avg warning threshold below it — alerting at 20 when gcs.fc_limit is 256 gives ample lead time. The relationship between fc_limit, fc_factor, and these metrics is developed in the wsrep.cnf Configuration Deep Dive.
Verification
Prove the alert fires on a real backlog before trusting it in production. Read the live values on each node and compare _sent across the group to confirm which member is the source:
-- Run on every node; the one with a rising _sent is the bottleneck.
SHOW GLOBAL STATUS WHERE Variable_name IN (
'wsrep_flow_control_paused', -- interval fraction, reset on read
'wsrep_flow_control_paused_ns', -- monotonic; sample twice for the rate
'wsrep_flow_control_sent', -- non-zero here = this node throttles the group
'wsrep_local_recv_queue_avg' -- the leading indicator
);
Force a backlog safely on a test cluster by throttling one node’s apply pipeline (drop its wsrep_slave_threads to 1 and drive a write burst), then watch wsrep_local_recv_queue_avg climb on that node and wsrep_flow_control_paused_ns rise across all members. Confirm the PromQL expression returns the expected fraction:
rate(galera_wsrep_flow_control_paused_ns[5m]) / 1e9
Edge Cases & Gotchas
- Never alert on
wsrep_flow_control_pausedas if it were cumulative. It resets each time status is read, so if your exporter and an ad-hocSHOW STATUSboth read it, each sees a fraction of the true window and under-reports. Alert onrate(wsrep_flow_control_paused_ns[...])instead — the nanosecond counter is monotonic and immune to who read it last. - A single spike is not a stall.
wsrep_flow_control_pausedlegitimately jumps for one interval during a large transaction or a batch load. Require the condition to hold across afor: 2m–3mwindow (or two to three consecutive Python samples) so momentary bursts do not page; the throttle is doing its job, not failing. - High
_recvwith low_sentmeans look elsewhere. If a node shows lots ofwsrep_flow_control_recvbut near-zerowsrep_flow_control_sent, it is a victim of the pause, not its cause — another member is the bottleneck. Always comparewsrep_flow_control_sentacross every node to attribute the stall to the correct host before you touch itswsrep_slave_threadsor disk.
Related
- Python Monitoring & Alerting Patterns for Galera — the collector and de-duplication design these alerts plug into
- Building a Prometheus Exporter for wsrep Status — the gauges the PromQL rules above consume
- Tuning Flow Control to Prevent Cluster Stalls — adjusting gcs.fc_limit and gcs.fc_factor once these alerts fire
- Write-Set Certification Process Explained — the certification pipeline that feeds the apply queue