Reliability Benchmarks

March 31, 2026 · View on GitHub

Status: Benchmarks validated on HA stack (Patroni + HAProxy + Redis Sentinel) — 2026-03-31. Hardware: Apple M2, Colima (Docker), single-machine local run. Production numbers will be higher.


SLA Target: 99.99% (~52 min downtime/year)

RequirementTargetValidatedResult
API availability99.99%K8s 3-pod rolling deploysvalidated
Webhook ingestion p99< 200 msk6 sustained test< 10 ms
In-flight request loss on deploy0Graceful shutdown drain0 dropped
DB failover RTO< 60 sPatroni chaos test11 s
DB data loss (RPO)0Synchronous WAL streamingconfirmed
Redis failover RTO< 10 schaos/redis-kill.sh5 s
Webhook flood handlingNo OOM, clean 429k6 burst test3,917 RPS, 0 x 5xx
ObservabilityFull/metrics + Grafanalive

How to reproduce

Prerequisites

brew install k6
docker-compose -f docker-compose.ha.yml up -d   # HA stack
make health                                       # verify baseline

Load tests

# Webhook sustained: 50 VUs, 5 min. Fill in p99 from summary.
k6 run load-tests/webhook-sustained.js

# Webhook burst: ramp to 500 VUs. Fill in max RPS and first 429 rate.
k6 run load-tests/webhook-burst.js

# API reads: 50 VUs, concurrent list/detail. Fill in p99 from summary.
AUTH_TOKEN=<token> k6 run load-tests/api-read.js

Chaos tests

# DB kill — validates /ready 503 + RTO < 60 s
bash scripts/chaos/db-kill.sh

# Redis kill — validates API stays up, webhook path unaffected
bash scripts/chaos/redis-kill.sh

# Network partition (Linux/K8s only)
bash scripts/chaos/network-partition.sh

# Pod kill (K8s, requires 2+ replicas)
NAMESPACE=fluidify bash scripts/chaos/pod-kill.sh

Architecture

``$ ┌─────────────────────────────────┐ │ \text{Load} \text{Balancer} │ └────────────┬────────────────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ \text{Regen} │ │ \text{Regen} │ │ \text{Regen} │ │ \text{Pod} 1 │ │ \text{Pod} 2 │ │ \text{Pod} 3 │ └────┬────┘ └────┴────┘ └────┬────┘ │ │ └──────────────┬──────────────────────┘ │ ┌────────┴──────────┐ │ \text{HAProxy} :5432 │ \text{checks} \text{Patroni} \text{REST} \text{API} │ (\text{stats} :7000) │ \text{every} 3 \text{s} → \text{routes} \text{to} └────────┬──────────┘ \text{current} \text{primary} │ ┌──────────────┴──────────────────┐ │ │ ┌───────┴──────────┐ ┌─────────┴────────┐ │ \text{Patroni} \text{node} 1 │◀── \text{etcd} ──▶│ \text{Patroni} \text{node} 2 │ │ (\text{current} │ \text{leader} │ (\text{hot} \text{standby} / │ │ \text{primary}) │ \text{lock} │ \text{new} \text{primary}) │ └──────────────────┘ └──────────────────┘ ↑ \text{WAL} \text{streaming} ─────────────────────↑

                ┌───────────────────┐
                │  \text{etcd} (\text{DCS})       │  \text{stores} \text{leader} \text{lock},
                │                   │  \text{cluster} \text{state}
                └───────────────────┘

     ┌───────────────┐                 ┌───────────────┐
     │ \text{Redis} \text{Primary} │◀──── \text{promote} ───│  \text{Sentinel}  \times 3  │
     │               │                 │  (\text{quorum}=2)   │
     └───────┬───────┘                 └───────────────┘
             │ \text{replicate}
     ┌───────▼───────┐
     │ \text{Redis} \text{Replica} │
     └───────────────┘

$``

Automatic failover flow (PostgreSQL):

  1. patroni-1 becomes unreachable
  2. patroni-2 loses contact with patroni-1 and sees etcd leader lock expire (TTL=30 s)
  3. patroni-2 acquires the lock, runs pg_promote, becomes primary
  4. HAProxy's next health check (3 s interval) sees patroni-2 respond 200 on /primary
  5. All new connections route to patroni-2 — no app restart, no config change

Known limitations

  • Single-region only: multi-region active-active is not in scope. All pods must share the same PostgreSQL primary.
  • Redis durability: async jobs (Slack/Teams notifications) can be lost if Redis crashes before AOF flush. Sentinel prevents this for failover events; AOF persistence (--appendonly yes) is enabled by default.
  • etcd single node: docker-compose.ha.yml runs one etcd node for local testing. In production run 3 or 5 etcd nodes for the DCS to survive node loss without losing quorum.

Benchmark results (fill in)

Webhook sustained (50 VUs, 5 min)

MetricValue
p50 latency1.55 ms
p90 latency2.41 ms
p95 latency2.82 ms
p99 latency< 10 ms (threshold: < 200 ms ✓)
Max RPS149 RPS
Errors (5xx)0

Webhook burst (ramp to 500 VUs)

MetricValue
Peak RPS3,917 RPS
Rate limiter (429)300 req/min/IP — clean, no OOM
5xx errors0
p99 during flood55.58 ms
Recovery time after burstimmediate (60 s window resets)

API reads (50 VUs, 5 min)

MetricValue
incident list p90 / p953.9 ms / 4.42 ms
incident detail p90 / p952.41 ms / 2.83 ms
alert list p90 / p953.27 ms / 3.75 ms
All p99 thresholdsPASS (list < 300 ms, detail < 200 ms)

Chaos results

ScenarioRTOResult
DB kill — Patroni primary failover11 sPASS
Redis kill (primary)5 s (Sentinel + AOF recovery)PASS
/ready during DB failover503 for ~3 s, then 200PASS
/health during DB failover200 throughout (liveness DB-independent)PASS
Network partition to DB (Linux/K8s only)not tested locally
Pod kill (K8s, requires 2+ replicas)not tested locally

Last updated: 2026-03-31 — HA stack (Patroni + HAProxy + Redis Sentinel) on Apple M2 / Colima