Leader Election concept page: bully algorithm, lease-based election (etcd/Consul), split-brain without quorum, fencing token protection, real-world Patroni for Postgres HA. 5 nodes + external coordinator store.
Multi-node системе часто нужен один координатор: для writes (primary), для job scheduling (cron leader), для config (controller). Leader election это процесс выбора этого узла + детекции его падения + переизбрания нового без split-brain.
Распределённые системы любят симметрию: N одинаковых нод, любая может обработать запрос. Но многие операции не коммутативны и требуют единого координатора:
Leader election это всегда trade-off между двумя плохими исходами: no leader (система зависла, writes отвергаются) и two leaders (split-brain, writes расходятся). Хороший алгоритм минимизирует window без leader и математически исключает двух leader-ов одновременно.
Представь команду из 5 человек, и им нужно выбрать капитана. Все равны, голосования нет. Правила:
Безопасность достигается одним из трёх способов:
Главная ловушка: сеть ненадёжна, и узел A не может отличить «B упал» от «B жив, но я его не вижу». Поэтому любой алгоритм, который полагается только на heartbeats и не использует quorum/lease, рано или поздно даст split-brain.
Кластер из 5 узлов (node-1..node-5) и внешний coordination store (etcd / Consul). Между узлами — election edges (для классических алгоритмов), от каждого узла — edge к store (для lease-based). Это сознательно избыточная топология: разные сценарии используют разные подмножества рёбер.
state: 'running' по умолчанию, flashError помечает падение, showError помечает невалидные сценарии (split-brain).ADR на node-1 ([ADR-001] Leader election: один координатор без split-brain) суммирует выбор подхода и почему quorum + fencing token это must-have.
Пять сценариев, расположены от классического к production-grade:
node-1 обнаруживает отсутствие leader-а, шлёт ELECTION всем с higher ID. node-4 отвечает, перехватывает выборы, шлёт ELECTION выше, не получает ответа от node-5 (down), объявляет себя COORDINATOR. Показывает: алгоритм работает, но генерирует O(N²) сообщений в худшем случае. На 100 узлах это неприемлемо.node-1 делает CompareAndSwap в etcd с TTL 30s, становится leader-ом, каждые 10s продлевает heartbeat. При падении lease истекает за 30s, node-2 делает CAS, становится новым leader-ом. Это паттерн Kubernetes, Patroni, ClickHouse Keeper.{1,2} и {3,4,5}. Обе стороны независимо избирают leader-а, обе принимают writes, данные расходятся. showError подсвечивает: «BOTH WRITE» — это корректное поведение алгоритма без quorum, и именно поэтому Bully/Ring без дополнительной защиты не подходят для production.node-1 лидерствует с token=42, теряет сеть, переизбирается node-3 с token=43. Когда node-1 «воскресает» и пытается записать с token=42, storage возвращает 409 STALE TOKEN. Это спасает от Old GC pause / process freeze, когда старый leader не знает, что его уже сместили (классический пример Kleppmann)./service/cluster/leader в etcd с TTL. При падении primary PG-1 lease истекает, PG-2 делает CAS, выполняет pg_promote(), становится writable. Прокси (HAProxy / PgBouncer / pgcat) переключает клиентский трафик на новый primary.Каждый сценарий запускается отдельно через scenario pills в плеере. Прогресс по концепту не трекается — это free wikipedia-style чтиво.
Context: нужен один координатор в кластере N узлов, при падении быстрый failover, без split-brain, без потерянных writes.
Decision matrix:
| Подход | Failover speed | Split-brain safe | Сложность | Когда выбирать |
|---|---|---|---|---|
| Bully | O(N²) сообщений, медленно | Нет (нужен quorum поверх) | Низкая | Legacy / lab |
| Ring | O(N) сообщений, ещё медленнее | Нет | Низкая | Учебный пример |
| Lease (etcd/ZK/Consul) | TTL (15-30s typical) | Да (CAS atomic) | Средняя | Default для production |
| Consensus (Raft/Paxos) | 1-2 RTT | Да (встроено) | Высокая | Если уже есть Raft (etcd, CockroachDB) |
| Gossip-based | Eventually | Нет (transient split допустим) | Средняя | AP-системы (Cassandra peer discovery) |
Decision: lease-based с fencing token. Аргументы:
Consequences:
| Система | Алгоритм | Storage |
|---|---|---|
| PostgreSQL + Patroni | Lease | etcd / Consul / ZooKeeper |
| MongoDB replica set | Raft-like (own impl) | Built-in |
| Kafka Controller | ZK ephemeral node → KRaft Raft (post 3.3) | ZooKeeper / KRaft |
| Kubernetes | Lease (coordination.k8s.io/Lease) | etcd via apiserver |
| Redis Sentinel | Quorum-based vote | Sentinel mesh |
| ClickHouse Keeper | Raft (NuRaft) | Built-in |
| HashiCorp Consul | Raft | Built-in |
| etcd | Raft | Built-in |
| Spark | ZK leader latch | ZooKeeper |
| Elasticsearch | Zen Discovery → Raft-like (post 7.0) | Built-in |
| CockroachDB | Raft per range | Built-in |
Паттерн: на верхнем уровне почти все production системы либо используют внешний lease store (Patroni, K8s, Spark), либо встраивают Raft (etcd, CockroachDB, ClickHouse Keeper, modern Kafka). Bully/Ring остались в учебниках и legacy distributed Erlang.
O(N²) election traffic на каждую смену leader. На 100 узлах при flapping сети сеть зальёт election-сообщениями и сделает только хуже.UPDATE leader SET node='me' WHERE TRUE без условия на текущего владельца это race condition. Должно быть UPDATE leader SET node='me', token=token+1 WHERE token=$current_token.Не каждая система нуждается в leader-е. Альтернативы:
version=N и ретраить, чем держать постоянный координатор.SELECT ... FOR UPDATE SKIP LOCKED в Postgres. Leader election на уровне приложения — это overkill.Если задача укладывается в любой из этих паттернов — leader election это лишняя сложность и лишний failure mode.
::concept{slug="consensus-overview"} — почему leader election сводится к consensus problem.::concept{slug="raft"} — Raft protocol (term, log replication, leader election as part of consensus).::concept{slug="paxos"} — классический Paxos.::case{slug="kafka-broker-internals"} — controller election в Kafka (KRaft Raft).::case{slug="postgres-patroni-ha"} — Postgres HA через Patroni + etcd lease.::case{slug="kubernetes-cluster"} — leader election в K8s controller-manager через Lease object.