Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Leader election: выборы лидера в кластере

Leader election: как выбирают лидера и чем опасна аренда

Выборы лидера: lease и TTL, алгоритмы на etcd/ZooKeeper/Consul, fencing token, типичные гонки при failover и рабочие настройки таймаутов.

Прямой ответ

Leader election — процедура, при которой один узел кластера получает эксклюзивное право выполнять роль лидера: принимать запись, запускать планировщик, координировать ребалансировку. В системах на Raft выборы встроены в протокол; в прикладных сервисах лидерство обычно арендуют через внешнее согласованное хранилище — etcd, ZooKeeper, Consul или Kubernetes Lease.

Базовая схема на аренде (lease):

  1. Узел создаёт ключ лидерства с TTL (например, 15 секунд).
  2. Пока узел жив, он продлевает аренду каждые TTL / 3.
  3. Если продления нет, ключ expires, и остальные узелы устраивают гонку за создание ключа.
  4. Победитель становится лидером; проигравшие возвращаются в ожидание.

Критичные детали:

  • TTL и clock. Аренда опирается на время. Если процесс лидера «завис» (GC-пауза, swap, перегрузка), он может считать себя лидером, хотя аренда уже истекла и лидером стал другой узел. Это путь к split brain на уровне приложения.
  • Fencing token. Надёжная защита — монотонно растущий токен, который выдаётся вместе с лидерством (revision ключа в etcd, zxid/версия ноды в ZooKeeper). Хранилище данных отклоняет запись с токеном меньшим, чем уже виденный, поэтому «воскресший» старый лидер не сможет испортить данные.
  • Атомарность. Создание ключа должно быть условным: create if not exists (etcd Txn с CreateRevision == 0, ZooKeeper CREATE ... 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.

Практические рекомендации:

  1. Не полагайтесь только на TTL. Добавьте fencing на стороне хранилища данных: без него пауза процесса превращается в два лидера.
  2. Фиксируйте смену лидера метрикой. Счётчик смен лидера и длительность «без лидера» должны быть на дашборде — см. SLO-дашборды.
  3. Проверяйте деградацию. Лидерство не должно «прилипать» к одному узлу навсегда: периодический добровольный отказ (leader step-down) помогает выявлять проблемы promote-пути заранее.

Практический сценарий: два лидера из-за GC-паузы

Контекст: сервис-планировщик в трёх репликах, лидерство через etcd-аренду с TTL 15 секунд.

  1. Событие. Лидер уходит в долгую GC-паузу (20 секунд) из-за всплеска аллокаций. Аренда истекает, лидером становится вторая реплика.
  2. Конфликт. Первая реплика выходит из паузы, считает себя лидером (проверка была сделана до паузы) и запускает задачу повторно — задание выполняется дважды.
  3. Диагностика. В логах видно две записи became leader с пересечением по времени; метрика leader_changes_total выросла, но fencing отсутствует, поэтому дубль прошёл.
  4. Исправление. (а) Проверяем лидерство после критической секции и перед каждой опасной операцией, а не один раз в начале; (б) передаём revision etcd как fencing token в хранилище задач, которое отклоняет устаревшие токены; (в) ограничиваем время GC-пауз и добавляем алерт на jvm_gc_pause_seconds.
  5. Проверка. Тест с искусственной приостановкой процесса (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» — ссылки в шапке.