Skip to content

Commit ed3310a

Browse files
h4x3rotabclaude
andcommitted
docs(consul-postgres-ha): stage 4 hard-kill failover variant + measured RTO
Extends FAILOVER.md with the whole-userspace failure scenario: kill all containers on the leader CVM simultaneously, then bring them back via `docker compose up -d`. Measured RTO ~33s (9s longer than soft-kill due to Consul gossip-failure detection on top of Patroni's TTL). Also confirms best-replica selection under uneven replica lag, QUIC mesh-conn ICE redial after a peer's userspace evaporates, and cheap rejoin via local WAL replay (no pg_basebackup). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f2ae489 commit ed3310a

1 file changed

Lines changed: 66 additions & 5 deletions

File tree

consul-postgres-ha/stage4/FAILOVER.md

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,76 @@ Consul KV:
114114
A common production setting is `ttl=20, loop_wait=5, retry_timeout=5`
115115
for ~10–15s RTO. Don't go below `ttl >= 2 * loop_wait` (Patroni rejects).
116116

117+
## Hard-kill variant (whole-userspace failure)
118+
119+
Same outline, but instead of stopping just `dstack-patroni-1`, simulate
120+
a "host crashed but recovered" scenario by killing all containers on
121+
the leader at once:
122+
123+
```bash
124+
ssh ... root@${LEADER}-22.${GW} "docker stop -t 0 \$(docker ps -q)"
125+
```
126+
127+
This kills patroni, postgres, mesh-conn, consul, sidecar, webdemo, and
128+
the keepalive — everything that produces signal for the rest of the
129+
cluster. Bring the host back via:
130+
131+
```bash
132+
ssh ... root@${LEADER}-22.${GW} \
133+
"cd /tapp && docker compose --env-file /dstack/.host-shared/.decrypted-env \
134+
-p dstack -f /tapp/docker-compose.yaml up -d"
135+
```
136+
137+
`docker compose up -d` respects the dependency order
138+
(bootstrap-secrets → mesh-conn → consul → patroni).
139+
140+
### Measured timeline (run from 2026-05-03)
141+
142+
```
143+
T_kill 07:26:42 docker stop -t 0 ALL 7 containers on worker-4
144+
T_new_leader 07:27:13 worker-3 promoted (timeline 16 → 17) +31s
145+
T_first_write 07:27:15 INSERT succeeds on worker-3 +33s ← RTO
146+
T_restart_W4 07:27:46 docker compose up -d on worker-4
147+
T_W4_rejoined 07:28:34 worker-4 streaming, lag=0 +48s after restart
148+
```
149+
150+
**Hard-kill RTO ≈ 33 seconds**, ~9 seconds longer than the soft-kill
151+
above. That extra cost is Consul gossip-failure detection: with
152+
soft-kill only the Patroni leader-key TTL expires, while with hard-kill
153+
the entire Consul agent is gone, so the surviving peers see *both*
154+
signals.
155+
156+
### Things confirmed by the hard-kill that the soft-kill didn't exercise
157+
158+
- **Best-replica selection under uneven lag.** Going into the kill,
159+
worker-3 was timeline=16, lag=0 while worker-5 was timeline=15 with
160+
measurable lag. Patroni picked worker-3 (the up-to-date one), not
161+
the alphabetically-earlier one. The promote-best-replica heuristic
162+
works.
163+
- **mesh-conn QUIC ICE redial after a peer's userspace evaporates.**
164+
Other peers' QUIC links to worker-4 hit `MaxIdleTimeout=60s` and
165+
tore down; once worker-4's containers came back, the new mesh-conn
166+
established fresh ICE pairs and replication resumed without
167+
intervention. The earlier yamux build had a pathology where
168+
redial-after-stress would loop forever; QUIC is clean.
169+
- **Cheap rejoin survives hard-kill.** worker-4's pgdata was
170+
untouched (the kernel never died, just userspace), so on bring-up
171+
Patroni replayed local WAL and joined as a streaming replica on the
172+
new timeline. No pg_basebackup, no multi-MB re-copy through
173+
mesh-conn.
174+
117175
## What this demo does NOT cover
118176

119-
* **Hard CVM kill** (whole VM down, not just the patroni container).
120-
RTO should be similar but exercises dstack/CVM-level recovery; mesh-conn
121-
ICE will need to redial when the old CVM comes back.
177+
* **CVM reboot or kernel panic**`reboot`/`poweroff` from inside
178+
the CVM. This involves the dstack platform's CVM lifecycle and is
179+
qualitatively different from container-level kills. Consider
180+
separately if/when you need to claim "host hardware failure"
181+
resilience.
122182
* **Network partition**: split-brain isolation between coordinators
123183
vs workers. Patroni + Consul should handle it, but worth a separate
124184
test before claiming partition-tolerance.
125185
* **Disk loss on rejoin**: if the ex-leader's pgdata is wiped, rejoin
126186
WILL trigger a full pg_basebackup through mesh-conn. The
127-
~25 MB/s throughput and the QUIC transport mean that even a 10 GB
128-
rebuild takes ~7 minutes (acceptable), but it's a different code path.
187+
~25 MB/s throughput and the QUIC transport mean even a 10 GB
188+
rebuild takes ~7 minutes (acceptable), but it's a different code
189+
path than the cheap rejoin shown above.

0 commit comments

Comments
 (0)