This is the multi-page printable view of this section. .
Failure Model
Patroni failures can be classified into 10 categories by failure target, and further consolidated into five categories based on detection path, which are detailed in this section.
| # | Failure Scenario | Description | Final Path |
|---|---|---|---|
| 1 | PG process crash | crash, OOM killed | Active Detection |
| 2 | PG connection refused | max_connections | Active Detection |
| 3 | PG zombie | Process alive but unresponsive | Active Detection (timeout) |
| 4 | Patroni process crash | kill -9, OOM | Passive Detection |
| 5 | Patroni zombie | Process alive but stuck | Watchdog |
| 6 | Node down | Power outage, hardware failure | Passive Detection |
| 7 | Node zombie | IO hang, CPU starvation | Watchdog |
| 8 | Primary ↔ DCS network failure | Firewall, switch failure | Network Partition |
| 9 | Storage failure | Disk failure, disk full, mount failure | Active Detection or Watchdog |
| 10 | Manual switchover | Switchover/Failover | Manual Trigger |
However, for RTO calculation purposes, all failures ultimately converge to two paths. This section explores the upper bound, lower bound, and average RTO for these two scenarios.
- Passive election triggered after Patroni loses contact
- Patroni actively detects failure and triggers switchover
flowchart LR
A([Primary Failure]) --> B{Patroni<br/>Detected?}
B -->|PG Crash| C[Attempt Local Restart]
B -->|Node Down| D[Wait TTL Expiration]
C -->|Success| E([Local Recovery])
C -->|Fail/Timeout| F[Release Leader Lock]
D --> F
F --> G[Replica Election]
G --> H[Execute Promote]
H --> I[HAProxy Detects]
I --> J([Service Restored])
style A fill:#dc3545,stroke:#b02a37,color:#fff
style E fill:#198754,stroke:#146c43,color:#fff
style J fill:#198754,stroke:#146c43,color:#fff
1 - Model of Patroni Passive Failure
infographic list-row-simple-horizontal-arrow
data
desc Lease Expiration Stages
items
- label Lease Expiration
- label Replica Detect
- label Elect & Promote
- label Haproxy Up
theme light
palette antvRTO Timeline
tooltip: { trigger: axis, axisPointer: { type: shadow }, formatter: $fn:fmt }
legend: { top: 0, itemGap: 12, data: [Lease Expiration, Replica Detection, Lock Contest & Promote, Health Check] }
grid: { left: 64, right: 24, bottom: 32, top: 40 }
xAxis: { type: value, name: Seconds, nameLocation: end, max: 160, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: true, lineStyle: { type: dashed, opacity: 0.5 } }, minorTick: { show: true, splitNumber: 5 }, minorSplitLine: { show: true, lineStyle: { type: dotted, opacity: 0.2 } } }
yAxis: { type: category, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: false }, axisLabel: { fontSize: 10, fontFamily: monospace }, data: [wide-max, wide-avg, wide-min, "", safe-max, safe-avg, safe-min, "", norm-max, norm-avg, norm-min, "", fast-max, fast-avg, fast-min] }
series:
- { name: Lease Expire, type: bar, stack: main, barWidth: 20, z: 2, emphasis: { focus: series }, itemStyle: { color: "#e15759" }, data: [120, 110, 100, "-", 60, 55, 50, "-", 30, 27, 25, "-", 20, 17, 15] }
- { name: Replica Detect, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#edc949" }, data: [20, 10, 0, "-", 10, 5, 0, "-", 5, 3, 0, "-", 5, 3, 0] }
- { name: Elect & Promote, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#59a14f" }, data: [2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0] }
- { name: HAProxy Check, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#4e79a7" }, data: [8, 6, 4, "-", 6, 5, 3, "-", 4, 3, 2, "-", 2, 2, 1] }
- { name: Total RTO, type: bar, barGap: "-100%", barWidth: 20, z: 1, itemStyle: { color: "#888", opacity: 0 }, emphasis: { itemStyle: { opacity: 0 } }, data: [150, 127, 104, "-", 78, 66, 53, "-", 41, 34, 27, "-", 29, 23, 16] }
- { name: RTO Budget, type: bar, barGap: "-100%", barWidth: 20, z: 0, itemStyle: { color: "rgba(0,0,0,0.08)" }, emphasis: { itemStyle: { color: "rgba(0,0,0,0.12)" } }, data: [150, 150, 150, "-", 90, 90, 90, "-", 45, 45, 45, "-", 30, 30, 30] }Failure Model
| Phase | Best | Worst | Average | Description |
|---|---|---|---|---|
| Lease Expiration | ttl - loop |
ttl |
ttl - loop/2 |
Best: crash just before refresh Worst: crash right after refresh |
| Replica Detect | 0 |
loop |
loop / 2 |
Best: exactly at check point Worst: just missed check point |
| Election Promote | 0 |
2 |
1 |
Best: direct lock and promote Worst: API timeout + Promote |
| HAProxy Check | (rise-1) × fastinter |
(rise-1) × fastinter + inter |
(rise-1) × fastinter + inter/2 |
Best: state change before check Worst: state change right after check |
Key Difference Between Passive and Active Failover:
| Scenario | Patroni Status | Lease Handling | Primary Wait Time |
|---|---|---|---|
| Active Failover (PG crash) | Alive, healthy | Actively tries to restart PG, releases lease on timeout | primary_start_timeout |
| Passive Failover (Node crash) | Dies with node | Cannot actively release, must wait for TTL expiration | ttl |
In passive failover scenarios, Patroni dies along with the node and cannot actively release the Leader Key. The lease in DCS can only trigger cluster election after TTL naturally expires.
Timeline Analysis
Phase 1: Lease Expiration
The Patroni primary refreshes the Leader Key every loop_wait cycle, resetting TTL to the configured value.
- Best case: Failure occurs just before lease refresh (elapsed
loopsince last refresh), remaining TTL =ttl - loop - Worst case: Failure occurs right after lease refresh, must wait full
ttl - Average case:
ttl - loop/2
Phase 2: Replica Detection
Replicas wake up on loop_wait cycles and check the Leader Key status in DCS.
- Best case: Replica happens to wake when lease expires, wait
0 - Worst case: Replica just entered sleep when lease expires, wait
loop - Average case:
loop/2
Phase 3: Lock Contest & Promote
When replicas detect Leader Key expiration, they start the election process. The replica that acquires the Leader Key executes pg_ctl promote to become the new primary.
- Via REST API, parallel queries to check each replica’s replication position, typically 10ms, hardcoded 2s timeout.
- Compare WAL positions to determine the best candidate, replicas attempt to create Leader Key (CAS atomic operation)
- Execute
pg_ctl promoteto become primary (very fast, typically negligible)
- Best case: Single replica or immediate lock acquisition and promotion, constant overhead
0.1s - Worst case: DCS API call timeout:
2s - Average case:
1sconstant overhead
Phase 4: Health Check
HAProxy detects the new primary online, requiring rise consecutive successful health checks.
- Best case: New primary promoted just before check,
(rise-1) × fastinter - Worst case: New primary promoted right after check,
(rise-1) × fastinter + inter - Average case:
(rise-1) × fastinter + inter/2
RTO Formula
Sum all phase times to get total RTO:
Best Case
Average Case
Worst Case
Model Calculation
Substitute the four RTO model parameters into the formulas above:
Four Mode Calculation Results (unit: seconds, format: min / avg / max)
| Phase | fast | norm | safe | wide |
|---|---|---|---|---|
| Lease Expiration | 15 / 17 / 20 |
25 / 27 / 30 |
50 / 55 / 60 |
100 / 110 / 120 |
| Replica Detection | 0 / 3 / 5 |
0 / 3 / 5 |
0 / 5 / 10 |
0 / 10 / 20 |
| Lock Contest & Promote | 0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
| Health Check | 1 / 2 / 2 |
2 / 3 / 4 |
3 / 5 / 6 |
4 / 6 / 8 |
| Total | 16 / 23 / 29 |
27 / 34 / 41 |
53 / 66 / 78 |
104 / 127 / 150 |
2 - Model of Patroni Active Failure
infographic list-row-simple-horizontal-arrow
data
desc When Patroni is healthy but PostgreSQL crashes
items
- label Crash Found
- label Restart Timeout
- label Replica Detect
- label Elect Promote
- label HAProxy Check
theme light
palette antvRTO Timeline
tooltip: { trigger: axis, axisPointer: { type: shadow }, formatter: $fn:fmt }
legend: { top: 0, itemGap: 12, data: [ Crash Found, Restart Timeout, Replica Detection, Elect Promote, HAProxy Check] }
grid: { left: 64, right: 24, bottom: 32, top: 40 }
xAxis: { type: value, name: Seconds, nameLocation: end, max: 160, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: true, lineStyle: { type: dashed, opacity: 0.5 } }, minorTick: { show: true, splitNumber: 5 }, minorSplitLine: { show: true, lineStyle: { type: dotted, opacity: 0.2 } } }
yAxis: { type: category, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: false }, axisLabel: { fontSize: 10, fontFamily: monospace }, data: [wide-max, wide-avg, wide-min, "", safe-max, safe-avg, safe-min, "", norm-max, norm-avg, norm-min, "", fast-max, fast-avg, fast-min] }
series:
- { name: Crash Found, type: bar, stack: main, barWidth: 20, z: 2, emphasis: { focus: series }, itemStyle: { color: "#b07aa1" }, data: [20, 10, 0, "-", 10, 5, 0, "-", 5, 3, 0, "-", 5, 3, 0] }
- { name: Restart Timeout, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#f28e2c" }, data: [95, 95, 0, "-", 45, 45, 0, "-", 25, 25, 0, "-", 15, 15, 0] }
- { name: Replica Detect, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#edc949" }, data: [20, 10, 0, "-", 10, 5, 0, "-", 5, 3, 0, "-", 5, 3, 0] }
- { name: Elect Promote, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#59a14f" }, data: [2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0] }
- { name: HAProxy Check, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#4e79a7" }, data: [8, 6, 4, "-", 6, 5, 3, "-", 4, 3, 2, "-", 2, 2, 1] }
- { name: RTO Total, type: bar, barGap: "-100%", barWidth: 20, z: 1, itemStyle: { color: "#888", opacity: 0 }, emphasis: { itemStyle: { opacity: 0 } }, data: [145, 122, 4, "-", 73, 61, 3, "-", 41, 35, 2, "-", 29, 24, 1] }
- { name: RTO Budget, type: bar, barGap: "-100%", barWidth: 20, z: 0, itemStyle: { color: "rgba(0,0,0,0.08)" }, emphasis: { itemStyle: { color: "rgba(0,0,0,0.12)" } }, data: [150, 150, 150, "-", 90, 90, 90, "-", 45, 45, 45, "-", 30, 30, 30] }Failure Model
| Item | Best | Worst | Average | Description |
|---|---|---|---|---|
| Crash Found | 0 |
loop |
loop/2 |
Best: PG crashes right before check Worst: PG crashes right after check |
| Restart Timeout | 0 |
start |
start |
Best: PG recovers instantly Worst: Wait full start timeout before releasing lease |
| Replica Detect | 0 |
loop |
loop/2 |
Best: Right at check point Worst: Just missed check point |
| Elect Promote | 0 |
2 |
1 |
Best: Acquire lock and promote directly Worst: API timeout + Promote |
| HAProxy Check | (rise-1) × fastinter |
(rise-1) × fastinter + inter |
(rise-1) × fastinter + inter/2 |
Best: State changes before check Worst: State changes right after check |
Key Difference Between Active and Passive Failure:
| Scenario | Patroni Status | Lease Handling | Main Wait Time |
|---|---|---|---|
| Active Failure (PG crash) | Alive, healthy | Actively tries to restart PG, releases lease after timeout | primary_start_timeout |
| Passive Failure (node down) | Dies with node | Cannot actively release, must wait for TTL expiry | ttl |
In active failure scenarios, Patroni remains alive and can actively detect PG crash and attempt restart. If restart succeeds, service self-heals; if timeout expires without recovery, Patroni actively releases the Leader Key, triggering cluster election.
Timing Analysis
Phase 1: Failure Detection
Patroni checks PostgreSQL status every loop_wait cycle (via pg_isready or process check).
- Best case: PG crashes right before Patroni check, detected immediately, wait
0 - Worst case: PG crashes right after check, wait for next cycle, wait
loop - Average case:
loop/2
Phase 2: Restart Timeout
After Patroni detects PG crash, it attempts to restart PostgreSQL. This phase has two possible outcomes:
Path A: Self-healing Success (Best case)
- PG restarts successfully, service recovers
- No failover triggered, extremely short RTO
- Wait time:
0(relative to Failover path)
Path B: Failover Required (Average/Worst case)
- PG still not recovered after
primary_start_timeout - Patroni actively releases Leader Key
- Wait time:
start
Note: Average case assumes failover is required. If PG can quickly self-heal, overall RTO will be significantly lower.
Phase 3: Standby Detection
Standbys wake up on loop_wait cycle and check Leader Key status in DCS. When primary Patroni releases the Leader Key, standbys discover this and begin election.
- Best case: Standby wakes right when lease is released, wait
0 - Worst case: Standby just went to sleep when lease released, wait
loop - Average case:
loop/2
Phase 4: Lock & Promote
After standbys discover Leader Key vacancy, election begins. The standby that acquires the Leader Key executes pg_ctl promote to become the new primary.
- Via REST API, parallel queries to check each standby’s replication position, typically 10ms, hardcoded 2s timeout.
- Compare WAL positions to determine best candidate, standbys attempt to create Leader Key (CAS atomic operation)
- Execute
pg_ctl promoteto become primary (very fast, typically negligible)
- Best case: Single standby or direct lock acquisition and promote, constant overhead
0.1s - Worst case: DCS API call timeout:
2s - Average case:
1sconstant overhead
Phase 5: Health Check
HAProxy detects new primary online, requires rise consecutive successful health checks.
- Best case: New primary comes up right at check time,
(rise-1) × fastinter - Worst case: New primary comes up right after check,
(rise-1) × fastinter + inter - Average case:
(rise-1) × fastinter + inter/2
RTO Formula
Sum all phase times to get total RTO:
Best Case (PG instant self-healing)
Average Case (Failover required)
Worst Case
Model Calculation
Substituting the four RTO model parameters into the formulas above:
Calculation Results for Four Modes (unit: seconds, format: min / avg / max)
| Phase | fast | norm | safe | wide |
|---|---|---|---|---|
| Failure Detection | 0 / 3 / 5 |
0 / 3 / 5 |
0 / 5 / 10 |
0 / 10 / 20 |
| Restart Timeout | 0 / 15 / 15 |
0 / 25 / 25 |
0 / 45 / 45 |
0 / 95 / 95 |
| Standby Detection | 0 / 3 / 5 |
0 / 3 / 5 |
0 / 5 / 10 |
0 / 10 / 20 |
| Lock & Promote | 0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
| Health Check | 1 / 2 / 2 |
2 / 3 / 4 |
3 / 5 / 6 |
4 / 6 / 8 |
| Total | 1 / 24 / 29 |
2 / 35 / 41 |
3 / 61 / 73 |
4 / 122 / 145 |
Comparison with Passive Failure
| Phase | Active Failure (PG crash) | Passive Failure (node down) | Description |
|---|---|---|---|
| Detection Mechanism | Patroni active detection | TTL passive expiry | Active detection discovers failure faster |
| Core Wait | start |
ttl |
start is usually less than ttl, but requires additional failure detection time |
| Lease Handling | Active release | Passive expiry | Active release is more timely |
| Self-healing Possible | Yes | No | Active detection can attempt local recovery |
RTO Comparison (Average case):
| Mode | Active Failure (PG crash) | Passive Failure (node down) | Difference |
|---|---|---|---|
| fast | 24s | 23s | +1s |
| norm | 35s | 34s | +1s |
| safe | 61s | 66s | -5s |
| wide | 122s | 127s | -5s |
Analysis: In
fastandnormmodes, active failure RTO is slightly higher than passive failure because it waits forprimary_start_timeout(start); but insafeandwidemodes, sincestart < ttl - loop, active failure is actually faster. However, active failure has the possibility of self-healing, with potentially extremely short RTO in best case scenarios.
3 - Network Partition
infographic list-row-simple-horizontal-arrow
data
title Network Partition Failover Flow
desc Primary partitioned from DCS, Patroni proactively demotes to prevent split-brain, waits for TTL expiration before switchover
items
- label Primary Demote
desc Patroni demotes PG after retry timeout
icon mingcute/shield-fill
- label Lease Expiration
desc Leader Key TTL expires
icon mingcute/close-circle-fill
- label Replica Detection
desc Replica detects lease expiration, starts election
icon mingcute/key-2-fill
- label Lock & Promote
desc Replica acquires lock and promotes to new primary
icon mingcute/radar-fill
- label Health Check
desc HAProxy detects new primary online
icon mingcute/arrow-up-circle-fill
theme light
palette antvRTO Timeline
tooltip: { trigger: axis, axisPointer: { type: shadow }, formatter: $fn:fmt }
legend: { top: 0, itemGap: 12, data: [Primary Demote, Lease Expiration, Replica Detection, Lock & Promote, Health Check] }
grid: { left: 64, right: 24, bottom: 32, top: 40 }
xAxis: { type: value, name: sec, nameLocation: end, max: 160, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: true, lineStyle: { type: dashed, opacity: 0.5 } }, minorTick: { show: true, splitNumber: 5 }, minorSplitLine: { show: true, lineStyle: { type: dotted, opacity: 0.2 } } }
yAxis: { type: category, axisLine: { show: true }, axisTick: { show: true }, splitLine: { show: false }, axisLabel: { fontSize: 10, fontFamily: monospace }, data: [wide-max, wide-avg, wide-min, "", safe-max, safe-avg, safe-min, "", norm-max, norm-avg, norm-min, "", fast-max, fast-avg, fast-min] }
series:
- { name: Primary Demote, type: bar, stack: main, barWidth: 20, z: 2, emphasis: { focus: series }, itemStyle: { color: "#76b7b2" }, data: [50, 40, 30, "-", 30, 25, 20, "-", 15, 13, 10, "-", 10, 8, 5] }
- { name: Lease Expiration, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#e15759" }, data: [70, 70, 70, "-", 30, 30, 30, "-", 15, 15, 15, "-", 10, 10, 10] }
- { name: Replica Detection, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#edc949" }, data: [20, 10, 0, "-", 10, 5, 0, "-", 5, 3, 0, "-", 5, 3, 0] }
- { name: Lock & Promote, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#59a14f" }, data: [2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0, "-", 2, 1, 0] }
- { name: Health Check, type: bar, stack: main, z: 2, emphasis: { focus: series }, itemStyle: { color: "#4e79a7" }, data: [8, 6, 4, "-", 6, 5, 3, "-", 4, 3, 2, "-", 2, 2, 1] }
- { name: RTO Total, type: bar, barGap: "-100%", barWidth: 20, z: 1, itemStyle: { color: "#888", opacity: 0 }, emphasis: { itemStyle: { opacity: 0 } }, data: [150, 127, 104, "-", 78, 66, 53, "-", 41, 34, 27, "-", 29, 23, 16] }
- { name: RTO Budget, type: bar, barGap: "-100%", barWidth: 20, z: 0, itemStyle: { color: "rgba(0,0,0,0.08)" }, emphasis: { itemStyle: { color: "rgba(0,0,0,0.12)" } }, data: [150, 150, 150, "-", 90, 90, 90, "-", 45, 45, 45, "-", 30, 30, 30] }Failure Model
| Phase | Best | Worst | Average | Notes |
|---|---|---|---|---|
| Demote | retry |
loop + retry |
loop/2 + retry |
Patroni retries after detecting partition, demotes after timeout |
| Lease Expiration | ttl - loop - retry |
ttl - loop - retry |
ttl - loop - retry |
Remaining TTL time after demotion (approximately constant) |
| Replica Detection | 0 |
loop |
loop/2 |
Best: Right at detection point Worst: Just missed detection |
| Lock & Promote | 0 |
2 |
1 |
Best: Direct lock and promote Worst: API timeout + Promote |
| Health Check | (rise-1) × fastinter |
(rise-1) × fastinter + inter |
(rise-1) × fastinter + inter/2 |
Best: State changes before check Worst: State changes right after check |
Key difference between network partition and node crash:
| Scenario | Patroni State | PostgreSQL State | Lease Handling | Split-brain Risk |
|---|---|---|---|---|
| Node Crash (Expire) | Dies with node | Completely unavailable | Passive wait for TTL expiration | None |
| Network Partition (This scenario) | Alive but cannot access DCS | May still be running (needs active demotion) | Passive wait for TTL expiration | Yes, needs protection |
In network partition scenarios, the primary PostgreSQL may still be running and accepting writes, causing split-brain issues. Patroni solves this through active demotion: when unable to refresh Leader Key, proactively demotes PostgreSQL to read-only or shuts it down.
Timeline Analysis
Phase 1: Primary Demotion
When primary Patroni is network-partitioned from DCS, it cannot refresh Leader Key and starts retrying.
- Detection delay: After partition occurs, must wait for next
loop_waitcycle to detect - Retry phase: Patroni continuously retries DCS operations during
retry_timeout - Active demotion: After retry timeout, Patroni proactively demotes PostgreSQL (prevents split-brain)
Key design: Patroni requires constraint loop_wait + 2 × retry_timeout ≤ ttl to ensure primary demotes before TTL expires.
Phase 2: Lease Expiration
After primary demotion, Leader Key still exists in DCS, must wait for TTL to naturally expire.
Since the primary has demoted, waiting time during this phase is the remaining TTL time. Since partition detection and remaining TTL are negatively correlated (earlier partition means slower detection but longer remaining TTL), their sum is constant:
Note: Primary demotion + lease expiration total time still approximately equals ttl, same as expire failure.
Phase 3: Replica Detection
Replica wakes up in loop_wait cycle and checks Leader Key status in DCS.
- Best case: Replica wakes right when lease expires, wait
0 - Worst case: Replica just entered sleep when lease expires, wait
loop - Average case:
loop/2
Phase 4: Lock & Promote
After replica discovers Leader Key expired, it starts the election process.
- Best case: Single replica or directly acquires lock and promotes,
≈ 0 - Worst case: DCS API call timeout,
2s - Average case:
1s
Phase 5: Health Check
HAProxy detects new primary coming online, requires rise consecutive successful health checks.
- Best case:
(rise-1) × fastinter - Worst case:
(rise-1) × fastinter + inter - Average case:
(rise-1) × fastinter + inter/2
RTO Formula
Sum all phase times to get total RTO.
Since primary demotion + lease expiration ≈ ttl, network partition RTO formula is same as expire failure:
Best Case
Average Case
Worst Case
Model Calculation
Substituting the four RTO model parameters into the formulas:
Patroni constraint validation (loop + 2×retry ≤ ttl):
| Mode | loop | retry | TTL | loop + 2×retry | Meets constraint? |
|---|---|---|---|---|---|
| fast | 5 | 5 | 20s | 15s | ✓ Safe |
| norm | 5 | 10 | 30s | 25s | ✓ Safe |
| safe | 10 | 20 | 60s | 50s | ✓ Safe |
| wide | 20 | 30 | 120s | 80s | ✓ Safe |
Four mode calculation results (seconds, format: min / avg / max)
| Phase | fast | norm | safe | wide |
|---|---|---|---|---|
| Primary Demote | 5 / 8 / 10 |
10 / 13 / 15 |
20 / 25 / 30 |
30 / 40 / 50 |
| Lease Expiration | 10 |
15 |
30 |
70 |
| Replica Detection | 0 / 3 / 5 |
0 / 3 / 5 |
0 / 5 / 10 |
0 / 10 / 20 |
| Lock & Promote | 0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
0 / 1 / 2 |
| Health Check | 1 / 2 / 2 |
2 / 3 / 4 |
3 / 5 / 6 |
4 / 6 / 8 |
| Total | 16 / 23 / 29 |
27 / 34 / 41 |
53 / 66 / 78 |
104 / 127 / 150 |
Conclusion: Network partition RTO is same as expire failure (node crash), as the bottleneck is TTL expiration time.
Split-brain Protection
The biggest risk of network partition is split-brain: old primary may still be running and accepting writes. Patroni provides multiple protection mechanisms:
1. Primary Self-Demotion
Patroni’s core protection mechanism: when unable to refresh Leader Key, proactively demotes PostgreSQL.
2. Linux Watchdog
If Patroni process hangs and cannot execute demotion, Linux watchdog will force system restart.
3. Fencing Mechanism
Can configure fencing scripts to forcibly isolate old primary (e.g., disable network interface, stop service, etc.).
Special Scenarios
Scenario A: Primary partitioned from DCS, replicas normal
This is the most common network partition scenario, the main focus of this article.
- Primary Patroni cannot refresh Leader Key → Active demotion
- Replica normally detects TTL expiration → Elected as new primary
- RTO ≈ Expire failure RTO
Scenario B: Primary normal, replica partitioned from DCS
- Primary normally refreshes Leader Key
- Replica cannot participate in election (but replication can continue)
- No failover triggered, service continues normally
Scenario C: All nodes partitioned from DCS
- Primary demotes, replica cannot elect
- Cluster completely unavailable
- Requires manual intervention to restore DCS connectivity
Comparison with Other Failures
| Failure Type | Primary State | Lease Handling | RTO | Split-brain Risk |
|---|---|---|---|---|
| Expire Failure | Node crash | Passive wait TTL expiration | 16s ~ 150s | None |
| Crash Failure | PG crash, Patroni alive | Release after restart timeout | 1s ~ 111s | None |
| Network Partition | Alive but isolated from DCS | Passive wait TTL expiration | 16s ~ 150s | Yes, needs protection |
| Manual Switchover | Normal or failed | Direct release/acquire | 1s ~ 11s | None |
Key Insight: Network partition RTO is same as expire failure, but requires additional split-brain protection mechanisms.
Ensuring loop_wait + 2 × retry_timeout ≤ ttl constraint is the key design to prevent split-brain.