Two nodes both claim to be primary. Patroni's REST endpoint
disagrees with the connected PG instance about the leader, or two
Patroni instances both report role=primary. There is no
Patroni-specific preflight that refuses in this state; instead,
wal push refuses a segment that a divergent writer already
archived (raising a splitbrain.* code), because it cannot
determine which timeline is canonical.
- A
wal pushof a segment already archived by a divergent writer fails with asplitbrain.*code —splitbrain.system_identifier_mismatch(different cluster) orsplitbrain.content_mismatch(same cluster, conflicting content). There is no Patroni-specific preflight refusal. pg_hardstorage doctorflagsPatroni topologyas critical.curl http://patroni-a:8008/clusterandcurl http://patroni-b:8008/clustershow conflictingmembers[].rolefields.- Application logs show writes succeeding against both nodes.
This is the only scenario where the runbook starts with stop the agent, stop application traffic, and call your DBA team. The recovery path is operator-only because resolving split-brain involves choosing a winner and discarding the loser's writes.
Confirm split-brain (rather than slow Patroni state propagation):
for h in node-a node-b node-c; do
echo "=== $h ===" ; curl -s http://$h:8008/cluster
done
psql -h node-a -tAc "SELECT pg_is_in_recovery(), pg_current_wal_lsn();"
psql -h node-b -tAc "SELECT pg_is_in_recovery(), pg_current_wal_lsn();"If two nodes both return pg_is_in_recovery()=false, you have
split-brain. If one is true, Patroni is just slow to propagate;
wait a few cycles and re-check.
-
Pause
pg_hardstorageso it doesn't write any chunks tagged with the wrong timeline:systemctl stop pg_hardstorage
-
Pause application writes. Whatever your traffic-control mechanism is — load balancer, PgBouncer admin, application feature flag — drop writes until the cluster is reconciled.
-
Pick the winner. Engineering judgment, not a tool decision. Typically the node with the higher
pg_current_wal_lsn()and the most recent application-acknowledged commits. Capture both nodes' state for forensics:for h in node-a node-b; do pg_dumpall -h $h --schema-only > /tmp/$h.schema.sql psql -h $h -tAc "SELECT pg_current_wal_lsn(), pg_walfile_name(pg_current_wal_lsn());" done
-
Stop the loser. Whatever you don't pick:
ssh <loser-host> 'pg_ctl stop -D <pgdata> -m fast'
Mark the host as offline in Patroni:
patronictl pause --wait patronictl remove <cluster-name>
-
Reset Patroni state. Patroni's DCS keys (etcd, Consul, Zookeeper) hold the leader lock. Clear them so the surviving node's Patroni can re-establish leadership cleanly:
patronictl reinit <cluster-name> <loser-host> # re-bootstraps from winner patronictl resume
The
reinitrebuilds the loser's data dir from the winner viapg_basebackuporpg_rewind. -
Restart
pg_hardstorage. With Patroni back to one leader the pre-flight refusal clears:systemctl start pg_hardstorage pg_hardstorage doctor <deployment>
-
Take a fresh backup. The split-brain window may have left inconsistent WAL on either side; a fresh backup serves as the safe restore floor:
pg_hardstorage backup <deployment>
pg_hardstorage doctoris clean across the cluster.patronictl list <cluster-name>shows exactly one leader, the rest replicas streaming.pg_hardstorage status <deployment>shows the new leader as the endpoint, WAL lag near zero.- A test restore against the post-incident backup passes the verify gate.
There is no rollback for the loser's discarded writes. That is the nature of split-brain resolution — one node's transactions lose. If those transactions are recoverable from application logs, replay them via the application; if not, this is a data-loss event and must be reported per organisational policy.
- Append an audit event with the LSNs of both candidates and the decision.
- File a Patroni-side incident: split-brain implies the DCS
lost coherence (etcd quorum loss, Zookeeper partition,
loop_waitmisconfigured, fencing absent). - Review fencing setup. STONITH-style power-fencing or network-fencing prevents two-leader scenarios; Patroni's software-only fencing relies on cooperative shutdown.
- Re-enable application traffic only after at least one fresh backup has succeeded and the verify gate has passed.
- Plan a game-day exercise (
pg_hardstorage gameday run --scenario patroni_split_brainin v0.5+) to confirm the recovery procedure works under realistic conditions.