High availability¶
Run more than one replica of Muster when you want the web UI, ingestion and delivery to survive the loss of a node or a restart. The Helm chart defaults to one replica, and the compose example runs one. Two replicas with a PodDisruptionBudget are an option, not a requirement.
All replicas are active¶
Every replica serves the web UI, the API and ingestion, and runs the workers for ingestion, delivery and timers. The workers claim rows from PostgreSQL with a lease, so a timer such as an ack timeout, a Reminder or the end of a Snooze fires on whichever replica is free. A replica that stalls delays nothing beyond its lease.
The Leader¶
A few tasks must run once, not once per replica. One replica, the Leader, runs only those. The list is closed:
| Task | What it does |
|---|---|
| Telegram polling | long polling of getUpdates for Connections in that update mode; Telegram answers 409 to a second poller |
| Bridge polling | polls the events of Bridge Connections in the poll event delivery mode, with one cursor per Connection |
| Heartbeat and Stale checks | find Integrations that are Heartbeat lost and Alerts that went Stale |
| partitions and retention | create partitions ahead of time, drop expired ones, and delete what the retention periods remove |
| pruning | removes ended sessions, expired requests and links, the records of gone replicas, and the Request log past its bounds |
| database gauges | the metrics computed from the database: open Alert Groups, the ingestion backlog, delivery queues, Broken Destinations, Storms |
| the "alive" mark | every 30 seconds, so that a restarted Muster knows how long it was down |
| the outgoing heartbeat | the call to your dead man's switch |
| OIDC secret expiry check | raises MusterOIDCSecretExpiring |
| re-render after a key rotation | re-signs the buttons of open Root messages (Key rotation) |
Nothing else depends on the Leader. Every task is safe to run twice, because a handover can overlap for a moment.
How a replica becomes Leader¶
The Leader holds a session advisory lock in PostgreSQL on a dedicated connection, the session connection. Every
replica tries to take the lock every leader.ping_interval (5 seconds); one succeeds. The Leader proves that it still
holds the lock by pinging over that session at the same interval. If no ping succeeds for leader.fencing_timeout (15
seconds), it stops all Leader tasks and closes the connection, and it may compete for the lock again later.
When a Leader crashes, its connection closes and PostgreSQL releases the lock at once. When its node disappears or the
network splits, PostgreSQL would keep the session for hours by default. Muster sets idle_session_timeout on the lock
session and TCP keepalives, so PostgreSQL ends a silent session and releases the lock within leader.server_bound (30
seconds). The fencing timeout is shorter than that, so the old Leader normally stops before another replica can take
over. The values are built in; they are not settings.
sequenceDiagram
participant A as "Replica A (Leader)"
participant PG as "PostgreSQL"
participant B as "Replica B"
A->>PG: "ping over the lock session, every 5 s"
B->>PG: "try the lock, every 5 s: taken"
Note over A: "the node of A is lost"
Note over A: "A stops its Leader tasks after 15 s without a ping"
PG-->>PG: "ends the silent session within 30 s, the lock is free"
B->>PG: "try the lock: acquired"
Note over B: "B is the Leader: muster_leader is 1"
B->>PG: "writes the alive mark, runs the Leader tasks"
A handover after the loss of a node takes well under a minute. During it, Telegram and Bridge polling, the periodic checks and the outgoing heartbeat pause. Ingestion, delivery and timers do not.
When no replica leads¶
The gauge muster_leader is 1 on the Leader and 0 on the others. When the "alive" mark is older than 2 minutes
(leader.absence_notice), the web UI shows a notice that no replica is leading. The chart rule MusterNoLeader fires
when no replica has led for 2 minutes; see its runbook.
While there is no Leader:
- Bridge Connections in the
pollmode and Telegram Connections in the long polling mode receive no button presses and no Text commands. When a Leader returns, a Bridge press or Text command older thanbridge.event_max_age(1 hour) is dropped unanswered; a Telegram press is dropped unanswered when the Connection received no updates for longer thantelegram.press_max_age(1 hour) before it. Connections in thepushmode of a Bridge and the webhook mode of Telegram keep working. - No Integration becomes Heartbeat lost and no Alert goes Stale.
- Partition maintenance, retention and pruning pause; the gauges that only the Leader exports go stale.
- The outgoing heartbeat stops, so a dead man's switch alerts you.
Ingestion, delivery, timers, the web UI and the API keep working.
After downtime¶
When every replica was down, the next Leader reads the "alive" mark, records the period, and adds a Timeline entry to open Alert Groups. Timers that came due during the downtime fire once, collapsed. For a while the web UI says Muster is recovering.
The session connection¶
The Leader lock and the LISTEN connections need a real session. If the main database connection goes through PgBouncer
in transaction mode, set MUSTER_DATABASE_SESSION_HOST and MUSTER_DATABASE_SESSION_PORT (in the chart,
database.sessionHost and database.sessionPort), or MUSTER_DATABASE_SESSION_URL, to an endpoint that reaches
PostgreSQL directly or through a pooler in session mode. At start, Muster checks that advisory locks and LISTEN work
over it and stops with an error that names the setting otherwise. See PgBouncer.
Two replicas¶
In the Helm values:
replicas: 2
podDisruptionBudget:
enabled: true
maxUnavailable: 1
The PodDisruptionBudget keeps a node drain from stopping both pods at once. Spread the pods over nodes with
topologySpreadConstraints or affinity. See Install with Helm for the other values. PostgreSQL
is the single stateful component, so its own availability, such as a CloudNativePG cluster with two instances, sets the
limit of the installation.
Replicas of different minor versions run side by side only during a rolling upgrade.
Clocks¶
Replicas share one database and take the time from their own system clocks. Run NTP, or the time service of your cloud
provider, on every node and on the database server. A replica measures its clock against the database every minute and
exports it as muster_clock_skew_seconds; it logs a warning above process.clock_skew_warning (2 seconds), and the chart
rule MusterClockSkew fires after 5 minutes.
A skew moves the timestamps a replica writes and the timers and windows it evaluates, and it can make TOTP codes, OIDC ID
tokens and the timestamps of outgoing webhook signatures fail. It can also make the lease of a row or of the Leader lock
expire too early or too late, so that work runs twice or waits. See the MusterClockSkew
runbook, and check the clock with muster doctor.