Распределённые системы
Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтения
Твой etcd-кластер имеет 50 000 чтений в секунду, но только 200 записей в секунду. Если каждое чтение требует раунда консенсуса, кластер тратит в 250 раз больше работы на чтения, чем на записи. Но если обслуживать чтения с stale-follower-а, теряется линеаризуемость. Как production Raft-реализации обслуживают чтения с sub-миллисекундной latency, не жертвуя корректностью?
Pre-vote: предотвращение паразитных выборов
Когда нода возвращается после долгого partition, она всё это время увеличивала счётчик term. Её RequestVote приходит с очень высоким term, вынуждая здоровые follower-ы обновить свои term и понизить текущего лидера — хотя никакого реального отказа не было. Кластер терпит ненужные выборы.
Pre-vote добавляет необязательный dry-run перед любыми реальными выборами. Прежде чем увеличить term, candidate шлёт сообщение PreVote. Ноды отвечают “я бы за тебя проголосовал” или “нет”, не меняя никакого персистентного состояния. Только если большинство ответило бы “да”, candidate реально увеличивает term и запускает настоящие выборы.
Цена: один дополнительный RPC round при реально нужных выборах. Выгода: ноль паразитных смен лидера от возвращающихся нод. Pre-vote теперь стандарт в etcd, TiKV и Hashicorp Raft.
Learner: безопасное масштабирование кластера
Добавлять новую ноду напрямую как voter опасно: она стартует с пустым логом, и её медленный catch-up деградирует commit latency при каждой записи до догонки. В 3-нодовом кластере добавление 4-го voter-а также временно снижает fault-tolerance (кворум теперь 3 из 4 вместо 2 из 3).
Learner-ы (не голосующие члены) решают это. Learner получает AppendEntries и реплицирует лог как follower, но не учитывается в кворумах и не голосует в выборах. Оператор промоутит learner-а в voter только после того, как лаг его лога снижается до порога (обычно под 1000 entry позади). Промоуция — это single-server membership change, безопасная и прозрачная.
Learner-ы также используются etcd, Consul и Yugabyte для замены нод после отказа: новая нода учится, догоняет, промоутится, затем мёртвая нода удаляется. Availability никогда не падает ниже исходного кворума в процессе замены.
Snapshot и log compaction
Лог Raft неограниченно растёт. Однолетний кластер с 10k записей/с записал 315 миллиардов entry — лог занял бы терабайты, а его воспроизведение после краша заняло бы дни.
Snapshot компрессирует лог: периодически каждая нода сериализует полное состояние state machine плюс тройку (lastIncludedIndex, lastIncludedTerm) на диск, затем удаляет все log entry до этого index. Если follower отстал настолько, что лидер уже скомпрессировал нужные ему entry, лидер шлёт RPC InstallSnapshot вместо пропущенных AppendEntries — follower грузит snapshot как начальное состояние, затем воспроизводит только недавний хвост лога.
| Система | Дефолтный interval snapshot |
|---|---|
| etcd | Каждые 10 000 entry |
| CockroachDB | Каждые несколько секунд на range |
| TiKV | Настраиваемый, дефолт 200 МБ |
Частота snapshot — настраиваемый tradeoff: слишком часто амплифицирует fsync; слишком редко растит лог и замедляет recovery. Snapshot должен включать актуальную конфигурацию membership — забыть об этом классический баг в DIY Raft реализациях.
ReadIndex: линеаризуемые чтения без записи в лог
Наивный подход к линеаризуемым чтениям: лидер коммитит no-op log entry ради чтения, затем возвращает данные. Это сериализует чтения в лог — дорого (один полный раунд консенсуса на чтение).
ReadIndex устраняет запись в лог. При приходе чтения:
- Лидер запоминает текущий
commitIndex. - Лидер шлёт heartbeat большинству follower-ов, подтверждая, что он всё ещё активный лидер (предотвращает stale-лидера от обслуживания чтений после partition).
- Как только state machine лидера применяет entry до
commitIndex, лидер возвращает результат.
Цена: один heartbeat round-trip (обычно 1–5 мс intra-region). Никакого нового log entry. Чтения масштабируются независимо от throughput записей.
Lease read: sub-миллисекундная latency
ReadIndex всё ещё платит за сетевой round-trip на каждое чтение. Lease read идёт дальше: лидер держит lease — временное окно, в котором он гарантированно является единственным лидером (потому что никакие выборы не могут завершиться в течение lease duration). В период lease лидер обслуживает чтения из своей текущей state machine без RPC.
Lease duration обычно election_timeout × 0.9 (например 9 мс при election timeout 10 мс), оставляя 10% буфер для clock skew.
Условие корректности: lease read безопасен только если clock skew между лидером и follower-ами ограничен. Если часы лидера бегут значительно быстрее follower-ов, lease может истечь у follower-а до того, как лидер думает — follower стартует новые выборы, пока старый лидер ещё обслуживает чтения. Поэтому NTP/PTP-синхронизация — требование корректности для lease read, а не просто гигиеническая практика.
- fsync latency, NVMe + BBU
- 50–100 мкс
- fsync latency, cloud SSD (EBS gp3)
- 1–3 мс
- ReadIndex latency (intra-region)
- 1–5 мс
- Lease read latency
- менее 1 мс
- Interval snapshot etcd (дефолт)
- 10 000 entry
- TimeoutNow leadership transfer
- менее 10 мс недоступности
Почему lease read считается чувствительным к корректности, а ReadIndex — нет?
Новая нода добавляется в 3-нодовый Raft-кластер напрямую как voter. Почему это может временно деградировать commit latency?
- 01Нода возвращается после 30 минут offline. Без pre-vote что происходит с кластером?
- 02Какую информацию должен включать Raft snapshot помимо сериализованной state machine?
- 03ReadIndex описывается как требующий 'одного heartbeat round-trip на чтение'. Почему лидер не может пропустить это и сразу обслуживать чтения из своего текущего состояния?
Четыре расширения заполняют пробел между учебным Raft и production-системами. Pre-vote устраняет паразитные выборы, требуя dry-run до увеличения term. Learner-ы позволяют безопасно масштабироваться, давая новым нодам догнать лог до учёта в кворуме. Snapshot ограничивает рост лога, периодически создавая checkpoint state machine и позволяя InstallSnapshot для отстающих follower-ов. ReadIndex обеспечивает линеаризуемые чтения с одним heartbeat round-trip вместо полной записи в лог; lease read доводит это до sub-миллисекунды, ограничивая авторитет лидера по времени, но требует NTP-синхронизации как предусловия корректности. Все четыре стандартны в etcd, TiKV и Hashicorp Raft.
встречается в178
- Почему 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
- 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
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: 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