Сети и протоколы
Session affinity, consistent hashing и правильное решение
Пользователь входит в систему. Его сессия живёт на backend B2. Следующий запрос маршрутизируется на B3. Сессия не найдена. Пользователь выброшен из системы в середине работы. Это происходит, когда приложение хранит состояние на одном backend и полагается на session affinity, чтобы это скрыть — пока affinity не сломается.
Зачем нужны sticky sessions
Некоторые приложения хранят состояние сессии на backend — в памяти или в локальном файле. Если следующий запрос маршрутизируется на другой backend, состояния нет: пользователь выброшен из системы, корзина пуста, контекст загрузки утерян.
Sticky sessions (session affinity) заставляют LB маршрутизировать все запросы от одного клиента на один и тот же backend. Запросы клиента «прикреплены» к одному серверу.
Cookie-based affinity
LB устанавливает cookie при первом ответе:
Set-Cookie: LB_ROUTE=backend_2; Path=/При каждом последующем запросе браузер отправляет этот cookie. LB читает его и маршрутизирует на backend_2.
Преимущества:
- Переживает смену IP клиента (handover мобильной сети, NAT failover).
- Допускает graceful failover: если
backend_2нездоров, LB может выбрать новый backend и обновить cookie.
Недостаток: Cookie-based affinity требует HTTP. Не-HTTP-протоколы использовать его не могут.
IP-hash affinity
Маршрутизация по Hash(client_IP) % num_backends. Cookie не нужен — работает для любого протокола.
Недостатки:
- Ломается при смене IP клиента (handover мобильной сети, переподключение VPN).
- Плохо кластеризует: 50 клиентов за одним корпоративным NAT все хешируются на один backend — этот backend получает нагрузку в 50 раз больше остальных.
- Дисбаланс нагрузки при sticky sessions
- в 1,5–2,5 раза хуже
- Потеря сессии при падении прикреплённого backend
- 100% для этой сессии
- Cookie-based: переживает смену IP
- да
- IP-hash: переживает смену IP
- нет
- IP-hash при 50 клиентах за NAT
- все 50 попадают на один backend
- Правильное решение: TTL сессии в Redis
- любой backend может продолжить
Правильное решение: вынесенное состояние сессии
Храните данные сессии в распределённом кэше (Redis, Memcached, DynamoDB) по ключу session ID. Каждый backend читает из и пишет в этот хранилище.
Теперь LB может маршрутизировать запросы свободно с помощью power-of-two-choices. Без affinity. Без привязки. Данные сессии переживают отказы backends, потому что они в Redis, а не в памяти одного backend.
Этот паттерн используется в Netflix, Airbnb, Shopify и любом сервисе, масштабирующемся горизонтально.
Consistent hashing: для локальности кэша, не состояния сессии
Session affinity — о состоянии пользователя. Consistent hashing — о локальности кэша: маршрутизация ключа кэша на один и тот же backend для получения cache hit вместо miss.
Проблема round-robin для кэширования:
- Запрос
user:42→ B1 (кэшировано там). - Следующий запрос
user:42→ B2 (cache miss, запрос к БД). - Никакой локальности, никакой пользы от кэширования.
Consistent hashing отображает каждый ключ на точку кольца хешей. Backends тоже отображаются на точки (с множеством виртуальных узлов на backend для равномерного распределения, ~150–300). Ключ маршрутизируется к ближайшему backend по часовой стрелке на кольце.
Ключевое свойство: Когда backend входит или выходит, только ~1/N ключей переотображаются. Все остальные ключи остаются на том же backend. Это минимизирует нарушение кэша при изменениях топологии.
Стоимость поиска: O(log N) с отсортированным деревом (бинарный поиск по позициям виртуальных узлов).
Используйте consistent hashing для:
- Распределённых кэшей (Memcached shards, Redis cluster).
- Шардирования баз данных (маршрутизация по shard key).
- Sticky-маршрутизации запросов для локальности кэша (задачи кодирования медиа, аналитические агрегации).
Не используйте consistent hashing для общей балансировки запросов — power-of-two-choices адаптируется к реальной нагрузке в реальном времени, consistent hashing — нет.
Rendezvous hashing (highest-random-weight)
Альтернатива кольцевому consistent hashing:
- Для каждого backend вычислите
Hash(key, backend_id). - Маршрутизируйте на backend с наибольшим значением хеша.
Нет кольца, нет виртуальных узлов — проще реализовать. O(N) на поиск, но для малого N (<100 backends) разница незначительна. Распределение хеша зачастую более равномерно, чем у кольцевого consistent hashing на практике. Некоторые CDN и Facebook TAO используют rendezvous hashing для шардирования.
Почему это работает
Зачем важны виртуальные узлы. Без виртуальных узлов каждый backend занимает одну дугу на кольце. При 4 backends (A, B, C, D), размещённых в 0, 90, 180, 270 градусах, дуги идеально равномерны — но это идеальный случай. На практике hash(backend_id) кластеризует backends неравномерно. Виртуальные узлы отображают каждый backend на 150–300 позиций кольца, разбивая его на 150–300 малых дуг. Это усредняет распределение, чтобы ни один backend не занимал непропорционально большую дугу.
Расставьте шаги failover session affinity (cookie-based), показывающие, почему это не работает без Redis:
- 1 Клиент отправляет запрос с cookie LB_ROUTE=backend_2.
- 2 LB читает cookie и маршрутизирует на backend_2.
- 3 Backend_2 падает или становится нездоровым.
- 4 LB прекращает маршрутизировать новые запросы на backend_2, но не может перенести существующую сессию.
- 5 Сессия клиента теряется, потому что только backend_2 хранил её в памяти.
- 6 Клиент делает retry; LB выбирает новый backend, но данные сессии утеряны.
- 7 Правильное решение: хранить сессию в Redis, доступном с любого backend, чтобы failover был прозрачным.
В чём главный недостаток IP-hash session affinity по сравнению с cookie-based?
Когда backend удаляется из consistent-hash кольца, какая доля ключей должна переотображаться на новый backend?
- 01Почему session affinity считается антипаттерном и каково правильное решение?
- 02Какую проблему решает consistent hashing, которую не может решить round-robin?
- 03Что такое виртуальные узлы в consistent hashing и зачем они нужны?
Session affinity маршрутизирует каждого клиента на один backend через cookie (LB_ROUTE=backend_2) или IP-хеш. Cookie-based affinity переживает смену IP; IP-hash ломается при смене IP и плохо кластеризует за NAT. Оба вызывают дисбаланс нагрузки в 1,5–2,5 раза и теряют сессии при отказе backend — это обходные решения, а не настоящие. Правильная архитектура: вынести состояние сессии в Redis, чтобы любой backend мог продолжить любую сессию, а LB маршрутизировал свободно. Consistent hashing — другой инструмент для локальности кэша: он отображает ключ на один backend с переотображением ~1/N при изменении состава, используя виртуальные узлы (150–300 на backend) для выравнивания распределения по кольцу. Используйте consistent hashing для кэшей и шардирования; используйте power-of-two-choices для балансировки живых запросов.
встречается в152
- Почему 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
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит 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
- Что такое три сигнала: метрики, логи, трейсы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