Прямой ответ
Leader election — процедура, при которой один узел кластера получает эксклюзивное право выполнять роль лидера: принимать запись, запускать планировщик, координировать ребалансировку. В системах на Raft выборы встроены в протокол; в прикладных сервисах лидерство обычно арендуют через внешнее согласованное хранилище — etcd, ZooKeeper, Consul или Kubernetes Lease.
Базовая схема на аренде (lease):
- Узел создаёт ключ лидерства с TTL (например, 15 секунд).
- Пока узел жив, он продлевает аренду каждые
TTL / 3. - Если продления нет, ключ expires, и остальные узелы устраивают гонку за создание ключа.
- Победитель становится лидером; проигравшие возвращаются в ожидание.
Критичные детали:
- TTL и clock. Аренда опирается на время. Если процесс лидера «завис» (GC-пауза, swap, перегрузка), он может считать себя лидером, хотя аренда уже истекла и лидером стал другой узел. Это путь к split brain на уровне приложения.
- Fencing token. Надёжная защита — монотонно растущий токен, который выдаётся вместе с лидерством (revision ключа в etcd,
zxid/версия ноды в ZooKeeper). Хранилище данных отклоняет запись с токеном меньшим, чем уже виденный, поэтому «воскресший» старый лидер не сможет испортить данные. - Атомарность. Создание ключа должно быть условным:
create if not exists(etcdTxnсCreateRevision == 0, ZooKeeperCREATE ... EPHEMERAL). Проверка «ключа нет» и последующее создание отдельными шагами — гонка.
Практика: реализации в распространённых системах
etcd. Используется Txn с условием на CreateRevision и арендой (LeaseGrant, LeaseKeepAlive). Номер ревизии служит fencing token.
ZooKeeper. Эфемерная нода + сессионный механизм: при потере сессии нода удаляется автоматически, а уведомление watch будит остальных участников.
Kubernetes. Объект Lease в API: контроллеры и kubelet используют его для выборов и heartbeats. Для прикладных подов удобно брать клиентскую библиотеку client-go/tools/leaderelection с параметрами LeaseDuration, RenewDeadline, RetryPeriod.
# соотношения таймаутов для leaderelection в Kubernetes
leaseDuration: 15s # сколько живёт аренда без продления
renewDeadline: 10s # за сколько лидер обязан успеть продлить
retryPeriod: 2s # период попыток
Правило: leaseDuration > renewDeadline > retryPeriod. Обратные соотношения приводят к бесконечным «перевыборам на ровном месте».
Patroni. В PostgreSQL-кластере лидер держит ключ в DCS с TTL, а при его исчезновении узлы соревнуются за лидерство и выполняют promote — разбор в статье про автоматический failover Patroni.
Практические рекомендации:
- Не полагайтесь только на TTL. Добавьте fencing на стороне хранилища данных: без него пауза процесса превращается в два лидера.
- Фиксируйте смену лидера метрикой. Счётчик смен лидера и длительность «без лидера» должны быть на дашборде — см. SLO-дашборды.
- Проверяйте деградацию. Лидерство не должно «прилипать» к одному узлу навсегда: периодический добровольный отказ (leader step-down) помогает выявлять проблемы promote-пути заранее.
Практический сценарий: два лидера из-за GC-паузы
Контекст: сервис-планировщик в трёх репликах, лидерство через etcd-аренду с TTL 15 секунд.
- Событие. Лидер уходит в долгую GC-паузу (20 секунд) из-за всплеска аллокаций. Аренда истекает, лидером становится вторая реплика.
- Конфликт. Первая реплика выходит из паузы, считает себя лидером (проверка была сделана до паузы) и запускает задачу повторно — задание выполняется дважды.
- Диагностика. В логах видно две записи
became leaderс пересечением по времени; метрикаleader_changes_totalвыросла, но fencing отсутствует, поэтому дубль прошёл. - Исправление. (а) Проверяем лидерство после критической секции и перед каждой опасной операцией, а не один раз в начале; (б) передаём revision etcd как fencing token в хранилище задач, которое отклоняет устаревшие токены; (в) ограничиваем время GC-пауз и добавляем алерт на
jvm_gc_pause_seconds. - Проверка. Тест с искусственной приостановкой процесса (
SIGSTOP) подтверждает: вторая запись отклоняется по токену.
Результат: дублирование устранено без отказа от аренды. Вывод: аренда решает «кто лидер сейчас», а fencing token решает «чья запись действительна» — нужны оба механизма.
FAQ
Чем leader election отличается от распределённой блокировки? Цель одна — эксклюзивный доступ, но блокировка обычно короткая и привязана к конкретной операции, а лидерство длительное и связано с ролью узла. Требования к корректности у лидерства выше: долгая пауза лидера критичнее, поэтому fencing обязателен. Сравнение подходов к блокировкам — в статье про distributed lock на Redis и ZooKeeper.
Какой TTL выбрать? Компромисс между скоростью failover и риском ложных перевыборов. Ориентир: TTL в 3–5 раз больше типовой задержки продления и заметно больше p99.9 GC-пауз. Слишком короткий TTL при нагрузке на сеть даёт «выборную бурю», слишком длинный — долгий простой при реальном отказе.
Нужен ли лидер, если система на Raft? В самом Raft лидер выбирается протоколом, отдельный механизм не нужен. Но прикладные роли (планировщик задач, единственный writer в базу) часто требуют отдельного лидерства — тогда его строят поверх Raft-хранилища (etcd), чтобы решения были согласованными.
Как тестировать сценарии перевыборов?
Убивайте лидера (kill -9), приостанавливайте процесс (SIGSTOP/SIGCONT), рвите сеть между узлами и измеряйте время до нового лидера. Такие проверки входят в набор chaos-практик и требуют runbook — см. incident management и runbook.
Источники: etcd docs, Kubernetes docs (Lease), Kleppmann «How to do distributed locking» — ссылки в шапке.