Зачем нужно знать
Репликация — копия одних данных на нескольких нодах. Без неё не строится ни один production-grade сервис: умирает диск, падает rack, исчезает datacenter — а пользователь должен видеть «всё работает». Три практических навыка отсюда:
- Availability. Один primary без replica = одна авария = часы downtime. Reasonable failover RTO начинается с реплик.
- Read scalability. Read-нагрузка обычно в 10-100x больше write. Раскидать reads по followers — дешевле, чем шардить.
- Disaster recovery. Geo-replica переживает падение региона. Без неё RPO измеряется в часах snapshot-бэкапа.
Дорогой урок: «у нас async replication и автофейловер» звучит безопасно, пока в момент аварии не выясняется, что последние 3 секунды writes потеряны, а split-brain нарисовался потому что old leader при оживлении продолжил принимать writes. Этот документ — про то, как не наступать на эти грабли.
Mental model
Репликация — компромисс между свежестью данных, latency записи и сложностью conflict resolution. Sync = свежесть и zero loss, но writes ждут самого медленного. Async = быстрые writes и stale reads. Multi-leader снимает SPOF, но при partition требует conflict resolution. Leaderless с quorum (W+R>N) даёт extreme write availability ценой read-repair и eventual consistency.
Три измерения, по которым выбирают:
- Кто принимает writes — один leader / несколько leaders / любая нода.
- Когда ack клиенту — после local commit (async) / quorum (semi-sync) / всех (sync).
- Как мерджить конфликты — никак (single-leader) / LWW / vector clocks / CRDT / app-level.
Все остальные решения (Patroni, RDS Multi-AZ, writeConcern=majority, Cassandra QUORUM) — точки в этом 3D-пространстве.
Что показывает диаграмма
На канвасе — single-leader cluster: один Leader (us-east-1a, primary writes) и три follower-replicas: Follower-1 (us-east-1b, sync — кандидат на promote при failover), Follower-2 (us-east-1c, async, ~5ms lag) и Follower-CR (eu-west-1, cross-region async, ~100ms lag, для DR и read locality в EU). Слева — App / Client.
Edges:
- Writes — только Client → Leader (ровно одна стрелка для writes; это и есть «single-leader»).
- Reads — Client → любой Follower (load balancer round-robin / sticky / latency-based).
- WAL replication — Leader → followers (стрелки помечены sync / async + типичный lag).
Когда сценарий переключается на multi-leader или failover — то же железо, но с другим routing'ом writes и conflict resolution на heal.
Сценарии и что они учат
sync-write-happy — Sync replication, zero data loss
Клиент пишет, leader пишет WAL, ждёт ack от всех followers (включая cross-region), потом отвечает 200. Latency = latency самого медленного follower (~80ms из-за eu-west-1).
Что учит:
- Durability ↔ latency — это buy/sell.
synchronous_commit = remote_apply — нулевые потери, но write latency = max(replica latencies).
- Если leader умирает СРАЗУ после ack — данные точно есть на followers, RPO = 0.
- Почему cross-region sync редко включают: 80ms на write убивает throughput любого OLTP.
async-write-fast — Async replication, риск на failover
Leader коммитит локально (~1ms), сразу ack клиенту, followers догоняют в фоне.
Что учит:
- Replication lag = window потери данных. Если leader сгорит до replicate — последние N writes потеряны. Postgres async — 0.5-50ms; MySQL под нагрузкой — секунды.
- RPO > 0 OK для логов, аналитики, кешей; не для платежей, бронирования, инвентаря.
- Async — default почти везде (Postgres, MySQL, Mongo writeConcern=1). Включают «потому что быстрее», не понимая trade-off.
replication-lag-bug — Read-your-writes anomaly
Пользователь пишет коммент → leader ack за 1ms → load balancer на read выбрал eu-west replica с lag 100ms → возвращает comments БЕЗ нового. Пользователь: «коммент пропал».
Что учит:
- Replication lag — конкретный UX-баг, не абстракция.
- Решения по сложности: (1) read-from-leader window — N секунд после write роутим reads на leader; (2) monotonic reads — sticky на одну replica для сессии; (3) causal tokens — клиент носит LSN из write, follower ждёт; (4) read-your-writes consistency уровень в БД.
- Большинство «race conditions, которые не воспроизводятся на ноутбуке» — это replication lag.
failover-sequence — Automated failover via consensus
Leader умирает → 5 секунд heartbeat timeout → etcd/Patroni инициирует election → выбирается follower с самым большим LSN (sync replica) → promote → followers repoint → DNS обновляет endpoint → клиенты redirected. RTO ~15 секунд.
Что учит:
- Failover — это пять отдельных шагов, каждый может пойти не так: detection (false-positive из-за GC pause), election (split-brain без quorum), promotion (sync vs async — выбор не очевиден), routing (DNS TTL держит старый адрес), old leader handling (если оживёт без pg_rewind — split-brain).
- Promote async replica = потеря неприменённых writes. Promote sync replica = zero loss; если sync тоже умерла — деградация до async.
- Manual failover = 30-120 минут downtime. Автоматический (Patroni/Repmgr/cloud-managed) = 15-60 секунд.
- RDS Multi-AZ promise — «60-120 секунд», но с DNS propagation клиенты с длинными connection pools видят 2-3 минуты.
split-brain-multi-leader — Multi-leader split-brain & conflict
Два leader'а (us-east и eu-west) реплицируют bi-directionally. Partition → каждый продолжает принимать writes → heal → одна строка имеет TWO concurrent versions (name=Alice и name=Bob). Демонстрация трёх стратегий resolution.
Что учит:
- Multi-leader не убирает SPOF бесплатно — он перекладывает сложность на conflict resolution.
- LWW — детерминированно, но молча теряет данные (Bob затирает Alice). Default в Cassandra. OK для idempotent updates, не OK для счётчиков, set'ов, заказов.
- CRDT (LWW-Register, OR-Set, counters) — детерминированный merge без потерь; цена — переписать домен под CRDT-операции.
- Vector clocks + app-level merge (Dynamo, Riak) — сохраняем обе версии, app решает; цена — клиент должен уметь обрабатывать conflict.
- Multi-leader без quorum + partition = почти всегда боль. Use только когда tolerance к stale/conflict — явная проектная цель.
Trade-offs
ADR-001: Single-leader vs Multi-leader vs Leaderless
Status: accepted (default).
Context. Три топологии, три разных trade-off по write availability, conflict resolution и операционной сложности.
- Single-leader (Postgres, MySQL, Mongo replica set): один writer, упорядоченный log, тривиальная семантика. Минус — SPOF на write path; нужен автофейловер, RTO ~15с минимум.
- Multi-leader (CouchDB, мульти-master MySQL): writes в нескольких точках, но любая partition → concurrent writes одной строки → обязательное conflict resolution.
- Leaderless quorum (Cassandra, DynamoDB, Riak): любая нода принимает writes, W ack из N реплик. Extreme write availability, tunable W+R>N для strong reads. Цена — read-repair, anti-entropy, hinted handoff.
Decision. Default — single-leader с semi-sync replication + автофейловер (Patroni+etcd для Postgres; managed RDS/Aurora в AWS; Mongo replica set с writeConcern=majority). RPO≈0, RTO ~15-30с, простая операционка. Multi-leader — только для гео-распределённых каталогов с tolerance к conflict (CouchDB offline-first). Leaderless — только для write-heavy / time-series (>1M writes/sec) с eventual consistency.
Consequences. Принимаем SPOF на write и зависимость от качества автофейловера. Получаем простую модель консистентности и документированные failure scenarios.
ADR-002: Sync vs Async vs Semi-sync replication
Status: accepted (per-service).
Context. Sync = RPO=0 ценой latency = slowest replica. Async = минимальный latency, но RPO > 0 (writes в окне lag теряются при failover). Semi-sync (Postgres synchronous_commit=on с одной sync replica; MySQL semi-sync; Mongo writeConcern=majority) — компромисс: ack после ОДНОГО sync follower.
Decision. Per-service:
| Класс сервиса | Режим | Обоснование |
|---|
| Платежи, инвентарь, бронирование | Sync (writeConcern=majority, remote_apply) | RPO=0 обязателен; 5-30ms OK |
| Профили, заказы (OLTP) | Semi-sync (1 sync + N async) | RPO≈0 при типовых отказах |
| Логи, аналитика, telemetry | Async | RPO секунды OK, throughput критичен |
| Кеши, leaderboards | Async / weak (writeConcern=1) | Stale OK, перестроится |
Consequences. Каждый сервис декларирует RPO в SLO. Sync replicas — same region (LAN). Cross-region всегда async + явный RPO.
ADR-003: Conflict resolution (для multi-leader / leaderless)
Status: accepted.
Context. Multi-leader и leaderless могут породить concurrent writes одной строки. Стратегии: LWW (по timestamp), vector clocks (сохраняем обе версии), CRDT (детерминированный merge), app-level.
Decision. Зависит от типа данных:
- Idempotent overwrites (профиль пользователя — последний апдейт побеждает): LWW допустим. Принимаем silent loss редких concurrent edits.
- Счётчики, sets, корзины: обязательно CRDT (G-Counter, PN-Counter, OR-Set). LWW для счётчика = потеря инкрементов.
- Финансы, заказы, любой domain с инвариантами: НИКОГДА не multi-leader. Single-leader или leaderless с strong quorum (W+R>N, обычно W=2,R=2,N=3).
Consequences. Multi-leader выбираем сознательно. Резолверы документируем в ADR и тестируем chaos-тестами (kill cross-region link, проверь merge).
Реальные системы
- Postgres streaming replication — single-leader, sync/async/semi-sync configurable. Patroni+etcd для автофейловера (~15s RTO).
- MySQL InnoDB Cluster / Group Replication — single-leader с автофейловером, semi-sync default.
- MongoDB replica set — single-leader, configurable
writeConcern (1/majority/all), autoelection через Raft.
- Cassandra — leaderless quorum, tunable consistency (ONE/QUORUM/LOCAL_QUORUM/ALL), LWW default.
- DynamoDB — leaderless quorum под капотом; «eventually consistent» vs «strongly consistent» reads. Global Tables = multi-region multi-leader с LWW.
- AWS Aurora — single-writer, 6-way storage quorum (4-of-6 writes, 3-of-6 reads). Promote ~30с.
- AWS RDS Multi-AZ — single-leader, sync standby в другой AZ, failover 60-120с с DNS.
- Spanner — multi-writer через Paxos per shard, TrueTime для external consistency глобально.
- CockroachDB / TiDB — Raft per range, strong consistency, failover на уровне range.
- etcd / ZooKeeper / Consul — single-leader через Raft/ZAB для метаданных и leader election.
Anti-patterns
- Async replication + автофейловер без RPO в SLO. «У нас 99.99%» — а потом авария, потеряны 3 секунды платежей. RPO обязан быть явным.
- Multi-leader без conflict resolution. Поднимают мульти-мастер MySQL, надеются «conflict не случится», получают silent data loss при первой partition.
writeConcern: 1 в MongoDB для финансов. Ack после одного узла (primary). При failover теряются unreplicated writes. Для денег — majority минимум, лучше {w: majority, j: true}.
- Sync replication через cross-region. Latency 80-200ms убивает OLTP throughput. Cross-region — всегда async с явным RPO.
- Ручной failover в проде. 30-120 минут downtime, человеческие ошибки, забытый pg_rewind → split-brain. Patroni/Repmgr/cloud-managed — обязательно.
- Promote async replica «потому что sync умерла». Теряем неприменённые writes. Лучше короткий downtime, чем silent loss.
- Read-from-replica для recent writes без sticky. Replication lag ломает UX. Нужны sticky window, causal tokens, или просто читать с leader для recent.
- Старый leader при оживлении продолжает писать. Split-brain, две истории. Always —
pg_rewind / fence / consensus required to rejoin.
- TCP keepalive для detection failed leader. Keepalive — минуты по умолчанию. Нужен application heartbeat 1-5с через consensus.
Когда НЕ использовать
Странно для базовой техники, но: не каждый сервис нуждается в репликации.
- Stateless cache. Если можно перестроить за минуту из source-of-truth — один Redis лучше, чем два с replication overhead.
- Dev / staging. Single-instance без репликации — нормально. Это не prod.
- Embedded local stores. SQLite в desktop — нужен бэкап, не репликация.
- Append-only logs в object storage. S3 уже реплицирует (11 девяток), своя поверх — overengineering.
- Multi-leader «просто чтобы убрать SPOF». Анти-паттерн. Single-leader с автофейловером даёт ту же доступность за меньшую сложность.
- Sync на cross-region для общего OLTP. Кроме RPO=0 операций (финансы) — async + accepted RPO.
Дальше читать
- DDIA, Chapter 5 «Replication» — каноническое изложение трёх топологий, sync/async, replication lag, multi-leader conflict resolution. Обязательно.
- DDIA, Chapter 9 «Consistency and Consensus» — репликация сама по себе не даёт linearizability; нужен consensus.
- Postgres — High Availability, Load Balancing, and Replication — точная семантика
synchronous_commit.
- MongoDB — Replica Set Architectures — топологии (PSS, PSA), writeConcern, readConcern.
- Patroni docs — как работает automatic failover для Postgres (DCS, leader race, timeouts).
- AWS Aurora paper (SIGMOD 2017) — 6-way storage quorum, log-as-database.
- Dynamo paper (2007) — origin of leaderless quorum, vector clocks, hinted handoff.
- Spanner paper (2012) — multi-leader через Paxos + TrueTime, external consistency globally.
- [CONCEPT]cap-theorem — фундамент: почему репликация во время partition требует выбора C vs A.
- [CONCEPT]consensus-overview — Raft/Paxos, на которых стоит автоматический failover.
- [CONCEPT]leader-election — детально про шаг election в failover-цепочке.
- [CONCEPT]sharding — когда репликация уже не масштабирует writes.
- [CASE]payment-system — где sync replication обязательна.
- [CASE]chat — где async OK.
- [CASE]distributed-key-value-store — leaderless quorum в действии.