Распределённые системы
Raft в production: membership change, Multi-Raft и observability
Нужно заменить мёртвую ноду в 3-нодовом etcd-кластере. “Просто удали её и добавь новую” звучит просто — но сделано неправильно, это может оставить две группы нод, каждая из которых считает себя большинством. Joint consensus создан именно для предотвращения этого.
Membership change через joint consensus
Добавление или удаление нод из работающего Raft-кластера — самая error-prone операция в production. Наивный подход — атомарно поменять конфиг на всех нодах — создаёт окно несоответствия, в котором могут одновременно существовать два отдельных большинства.
Пример бага: переход с 3 нод (A, B, C) на 5 нод (A, B, C, D, E). Если разные ноды применяют смену конфига в разное время, можно ненадолго иметь: большинство старого конфига (2 из A, B, C) и большинство нового конфига (3 из A, B, C, D, E). Эти два большинства могут иметь нулевое пересечение — два лидера могут быть выбраны одновременно.
Joint consensus фиксит это: кластер переходит через промежуточную конфигурацию C_old_new, требующую большинства и от старой, и от новой конфигурации. Коммиты в этой фазе требуют большинства C_old И большинства C_new. Никакие два валидных кворума в этом окне не могут не пересекаться. Как только C_old_new закоммичено по всему кластеру, кластер переходит в C_new.
Hashicorp Raft и etcd реализуют joint consensus с дополнительным упрощением: только single-server изменения (добавлять или удалять по одной ноде). Это ограничивает joint-фазу простым двойным требованием большинства. Никогда не обходи joint consensus при membership change в production — Alibaba Cloud Raft engineering post документирует реальный 30-минутный split brain именно из-за этого сокращения.
| Фаза | Конфигурация | Правило кворума |
|---|---|---|
| До | C_old: A, B, C | Большинство C_old (2 из 3) |
| Переход | C_old_new | Большинство C_old И большинство C_new |
| После | C_new: A, B, C, D, E | Большинство C_new (3 из 5) |
Leadership transfer для rolling deploy
Без явного leadership transfer дрейнирование ноды-лидера перед обслуживанием вызывает одни выборы (150–300 мс недоступности) на каждую смену лидера. В Multi-Raft-системе с тысячами групп это неприемлемо.
TimeoutNow решает это: текущий лидер шлёт сообщение TimeoutNow назначенному целевому follower-у, который немедленно стартует выборы. Поскольку никакой другой follower ещё не таймаутил, цель почти всегда побеждает. Текущий лидер понижается. Пользователь-видимая недоступность — под 10 мс. CockroachDB и TiKV expose-ят leadership-transfer endpoint-ы именно для rolling deploy.
Multi-Raft и шардирование
Throughput одной Raft-группы ограничен fsync самой медленной реплики плюс network round-trip. Для высоконагруженных систем архитектура — Multi-Raft: множество независимых Raft-групп параллельно, каждая владеющая шардом keyspace.
- CockroachDB: партиционирует keyspace в ~512 МБ “ranges”, одна Raft-группа на range.
- TiKV: то же с ~100 МБ “regions”.
- Kafka KRaft: одна Raft-группа на партицию metadata topic.
Heartbeat coalescing делает Multi-Raft жизнеспособным в масштабе: вместо 50 000 групп × 1 heartbeat/50 мс = 1М heartbeat/с, 5-нодовый кластер с 50 000 групп шлёт 10 физических пакетов на 50 мс (один на пару нод), каждый содержащий все heartbeat-ы групп в компактном битмапе. Это 200 пакетов/с вместо 1М.
Минимально жизнеспособный Raft dashboard
| Метрика | Порог алерта | Корневая причина при срабатывании |
|---|---|---|
leader_changes_total в минуту | >1/мин | Flapping leadership — проверь диск или heartbeat RTT |
wal_fsync_duration_seconds p99 | >50 мс | Диск слишком медленный — перейди на NVMe |
commit_latency_seconds p99 | >100 мс | Диск, сеть или follower lag |
follower_lag_entries | >1000 | Follower отстаёт — скоро может понадобиться snapshot |
raft_term rate роста | >2/мин | Повторные выборы — проверь сеть + pre-vote |
Etcd поставляет etcd_server_leader_changes_seen_total, etcd_disk_wal_fsync_duration_seconds и etcd_network_peer_round_trip_time_seconds по умолчанию.
Три реальных production postmortem
GitHub октябрь 2018. Сетевой блип между датацентрами вызвал failover etcd. Misconfigured external sync писал в оба региона в краткое окно — Raft был корректен, оркестратор над ним — нет. Урок: Raft гарантирует консенсус внутри кластера; он не может защитить от misconfigured operator-слоя, пишущего мимо кластера.
Datadog 2023. TiKV-кластер испытывал повторные выборы лидера под высокой write-нагрузкой. Корневая причина: pre-vote был выключен в старой версии. Под write-давлением network jitter вызывал паразитные выборы, каждые из которых давали сотни миллисекунд недоступности. Включение pre-vote решило проблему. Урок: pre-vote — фича корректности, не nice-to-have.
Kubernetes etcd 2020. Админ удалил ноду из 3-нодового кластера без membership change API (вручную удалил data dir и переконфигурировал). Оставшиеся две ноды имели расходящиеся сохранённые конфигурации и циклически переключались между лидером и candidate. Фикс: остановить кластер, вручную поправить конфиги, рестарт. Урок: никогда не манипулируй Raft state вне собственного membership change path протокола.
Диагностируй лог Raft — что не так с follower D?
2026-05-13T15:42:08Z INFO raft: leader=A term=12 commit=400123
2026-05-13T15:42:08Z INFO raft: AppendEntries -> B success
2026-05-13T15:42:08Z INFO raft: AppendEntries -> C success
2026-05-13T15:42:08Z INFO raft: AppendEntries -> D failure (mismatch at idx=400100 term=12 vs follower idx=400100 term=11)
2026-05-13T15:42:09Z INFO raft: AppendEntries -> D retry prevIdx=400050 (decremented)
2026-05-13T15:42:09Z INFO raft: AppendEntries -> D failure (mismatch at idx=400050 term=12 vs follower idx=400050 term=10)
2026-05-13T15:42:10Z INFO raft: AppendEntries -> D retry prevIdx=399000 (decremented)
2026-05-13T15:42:10Z INFO raft: AppendEntries -> D failure (mismatch at idx=399000 term=12 vs follower idx=399000 term=8)
2026-05-13T15:42:11Z WARN raft: D has diverged extensively; consider InstallSnapshot Лидер постоянно уменьшает nextIndex для D. Каков диагноз и правильный операционный фикс?
Стартап нуждается в strongly consistent KV store для service discovery. Выбери реализацию консенсуса, лучше балансирующую корректность, latency и операционное бремя.
Почему наивная single-step смена конфига (добавление 3 нод одновременно без joint consensus) рискует split brain?
- 01Объясни, почему joint consensus предотвращает риск split brain при membership change.
- 02CockroachDB-кластер запускает 50 000 Raft-групп. Почему это не коллапсирует под 1М heartbeat в секунду?
- 03Каков минимальный набор Raft-метрик для алертинга и что каждая диагностирует?
Безопасный membership change требует joint consensus — перехода через двойной кворум, предотвращающего окно, в котором два непересекающихся большинства могли бы выбирать конкурирующих лидеров. Leadership transfer через TimeoutNow снижает недоступность при rolling deploy с сотен миллисекунд до под 10 мс. Multi-Raft масштабирует throughput кластера через параллельные группы с coalesced heartbeat-ами. Минимально жизнеспособный Raft dashboard отслеживает пять метрик: смены лидера, WAL fsync latency, commit latency, follower lag и rate роста term. Каждый реальный production-инцидент с Raft восходит к одной из трёх корневых причин: membership change, обойдённый через joint consensus, выключенный pre-vote или WAL на медленном cloud-объёме. Фикси все три превентивно и покроешь 90% production-отказов Raft.
встречается в185
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Event loop: один поток, три очередиjunior
- Задачи, микрозадачи и scheduler.yield()middle
- Голодание микрозадач, длинные задачи и LoAFsenior
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- React, Vue и наблюдаемость INP в продакшенеsenior
- Render pipeline: шесть стадий от байтов до пикселейjunior
- Цена стадий и модель процесса рендерераmiddle
- Инвалидация, dirty-биты и containmiddle
- Слои композитора: продвижение, перекрытие и память GPUmiddle
- Флейм-стрип DevTools и жизненный цикл кадраmiddle
- Layout thrash: форсированная синхронная компоновкаsenior
- BeginMainFrame, анимации на потоке compositor и память GPUsenior
- Observability в проде: LoAF, INP и полная поверхность атакиsenior
- Что такое V8 и почему производительность различается в 100 разjunior
- Четырёхуровневый JIT-конвейер V8 и профилированная тиеризацияmiddle
- Hidden classes, деревья переходов и расположение в памятиmiddle
- Inline caches, состояния IC и деоптимизацияmiddle
- Orinoco GC: параллельный scavenger, конкурентная разметка и барьеры записиmiddle
- Спекулятивный движок TurboFan и ловушка deopt-loopsenior
- V8 в production: Isolates, сжатие указателей и реальные аварииsenior
- Жизненный цикл service worker и стратегии кешированияmiddle
- Граничные случаи service worker: version skew, долговременность и ловушка навигацииsenior
- Что делает реконсилер: render vs commitjunior
- Объект fiber и дерево с двойной буферизациейmiddle
- Чистота фазы render и подшаги фазы commitmiddle
- Реконсиляция: эвристики диффа и ловушка ключейmiddle
- Приоритетные lanes, time-slicing и useTransitionmiddle
- Bailout, мемоизация и tearingsenior
- React Profiler, компилятор и продакшн-наблюдаемостьsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Hydration mismatch: причины, обнаружение и правило детерминизмаsenior
- RSC, стратегия на маршрут и production-наблюдаемостьsenior
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- CLS: почему происходят сдвиги лейаута и как их остановитьmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- HTTP: язык запрос-ответ в вебеjunior
- HTTP/2: потоки, фреймы и HPACKmiddle
- HTTP/3 и QUIC: изоляция потерь на уровне потокаmiddle
- HTTP/3 в продакшне: внутренности QUIC, fallback и наблюдаемостьsenior
- HTTP дизайн: приоритеты, WebTransport и семантическая корректностьsenior
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- QUIC-потоки и head-of-line blockingjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Критический путь рендеринга и Core Web Vitalsmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior