Модуль 6. Кластер и эксплуатация: failover, метрики, тест точности, чек-лист

Цель модуля: довести rate limiter до продакшена: что происходит с лимитом при отказе Redis, какие метрики и алерты обязательны, как доказать точность конкурентным тестом — и финальный чек-лист плюс итоговая карта применений Redis всего курса.

Перед началом: Модули 3–5 (лимитеры in/out), стенд make up-sentinel из курса «Redis как брокер» (мастер+реплика+Sentinel) или собственный. Трудоёмкость: ~8–10 ч (теория 3 ч · практика 6 ч).


🔥 Инцидент: «Фейловер Redis — и лимит на минуту исчез»

Sentinel честно отработал отказ: мастер умер, реплика поднялась мастером. Лимитер продолжал работать — но внезапно upstream получил в разы больше запросов, чем разрешено. Разбор: часть ключей бакетов не успела отреплицироваться, часть легла в другой слот кластера, а часы новой мастера ушли от часов приложения — окна «поехали». Формально всё живо, фактически лимит на минуту обнулился.

Вынос лимитера в Redis — это контракт с инфраструктурой: при фейловере состояние может опоздать или уехать; часы между узлами не совпадают; и «лимитер работает» не равно «лимит соблюдается». В модуле — что беречь в кластере (слоты, tolerant-скрипты против clock-skew), какие метрики и алерты показывают реальную точность, и как доказать её конкурентным тестом до того, как это сделает провайдер.

📖 6.1. Лимитёр в кластере Redis: слоты, фейловер, часы

Слоты. Ключ лимитера ratelimit:192.168.1.1 — одиночный ключ: он целиком живёт в одном слоте кластера, атомарность Lua не нарушается, hash tags не нужны (они нужны только когда одна операция трогает несколько ключей — у нас таких нет). Горячий лимит-ключ при экстремальном traffic — один горячий узел: лечится шардированием пространства (ratelimit:{shard0..7}:ip с выбором шарда по hash(ip)) — редко действительно нужно.

Failover (Sentinel). Смерть мастера → промоушен реплики → потеря хвоста репликации. Для лимитера это означает: на несколько секунд часть бакетов «омолодится» (потерянные списания вернутся) — лимит кратковременно пропускает чуть больше. Это осознанная цена: для защитных лимитов — приемлемо, для биллинга — paired с БД-квотами (точный учёт ведёт БД, Redis — быстрый предохранитель).

Часы. В Модуле 3 now передаёт приложение. Разброс часов подов (±100 мс после NTP) даёт погрешность refill — для лимитеров это шум. Хардкорный вариант — redis.call('TIME') в скрипте и запись после него запрещена в старых версиях — поэтому выбор остаётся за now-снаружи.

Graceful degradation (Модуль 4) при полном отказе Redis: hybrid уходит в local-only — сервис жив, лимит пер-инстансный. Биллинговый лимитер в этот момент fail-close: локальные лимитеры с урезанным лимитом (rps/N реплик) продолжают считать честно в пределах инстанса.


📖 6.2. Метрики и алерты лимитера

Минимальный набор (по нему ставится 4 алерта):

МетрикаАлерт
ratelimit_exceeded_total{source,route}нет (это нормальный бизнес-сигнал), но смотрите резкие всплески = атака
ratelimit_redis_errors_total> 0 за 5 мин — fallback активен, лимит ослаблен
ratelimit_allow_duration_seconds (p99)p99 Allow > 50 мс устойчиво — Redis деградирует
ratelimit_redis_errors → followed bypaired с redis_up/alarms из мониторинга Redis
deny ratio (exceeded/total по маршруту)резкий рост = сканер/сломанный клиент

source-лейбл (local/global/hybrid:local-fallback) из Модуля 4 — главная деталь: он показывает, кто именно принял решение, и делает деградацию видимой на графике (полоска fallback-режима не должна быть постоянной).


📖 6.3. Точность: конкурентный тест

Единственное доказательство кластерного лимита — число:

Запуск: 8 процессов × 8 горутин, каждый бьёт Allow по своему ключу «ip»
Ожидание: суммарно пропущено ≈ burst + refill за время теста
Замер: count allowed, count rejected

Критерии:

  • Точность: пропущено за окно ≤ burst + (прошло времени × rate) ± 1–2 (допуск на границу окон и параллельность часа). Если заметно больше — где-то неатомарный путь (вы забыли Lua или используете GET/SET);
  • Распределение: отказы размазаны по процессам (никто не «захвати» весь лимит);
  • Стабильность при фейловере: посреди теста make failover — допустим кратковременный пропуск (потеря хвоста), затем лимит восстанавливается;
  • Нет утечки памяти: redis-cli INFO keyspace — число ключей ratelimit:* не растёт бесконечно (TTL бакетов работает).

Интеграционный тест в CI: testcontainers Redis + 64 горутины × 500 запросов, assertion allowed ≤ burst + delta. Запускается за секунды и ловит регрессии атомарности навсегда.


📖 6.4. Чек-лист продакшена лимитера

Перед выводом в продакшен пройдите списком — каждый пункт это решение, а не настройка:

  • Политика отказа для каждой функции (fail-open/fail-close) — задокументирована и закомментирована в обёртке;
  • Ключи: IP только за доверенным прокси; user_id/API key после аутентификации; тест подмены XFF пройден;
  • TTL бакетов работает (нет роста ключей) — INFO keyspace;
  • Burst/rate согласованы с продуктом и документированы (клиентам — в API-доках);
  • Заголовки X-RateLimit-*/Retry-After отдаются на всех ответах;
  • PII в логах замаскировано (IP/user-id — сокращение/хеш);
  • Метрики exceeded/redis_errors/latency есть, 3 алерта настроены;
  • Конкурентный тест точности зелёный и в CI;
  • Chaos-тест отказа Redis пройден (fallback виден и ограничен);
  • Per-route лимиты (тяжёлые эндпоинты дороже) — если нужны.

📖 6.5. Итоговая карта Redis-применений курса

ПрименениеМодульПолитика при отказе Redis
Кэш (cache-aside, singleflight)М2fail-open: медленнее, но жив
Распределённые локиМ2fail-close: работу не берём
Rate limit входящий (гибрид)М3–М4fail-open (защита) / fail-close (тарифы)
Rate limit исходящий (внешние API)М5fail-open (вызвать) / fail-close (дорого превышать)
Очереди и потоки событийкурс «Redis как брокер»своя дисциплина (dual write, DLQ)
Идемпотентность (метки дедупа)М2 и брокер-курсfail-close на записи, TTL ≥ retention

Один Redis — четыре механизма с разными ценами отказа. Именно различение этих цен (а не «Redis упал») и есть инженерная зрелость, которую проверяет этот курс.


💻 Практика

  1. Конкурентный тест точности: 8 процессов × 8 горутин, свой ключ каждой; таблица «пропущено/отклонено/ожидание»; повторите с наивным GET/SET вместо Lua — увидите превышение.
  2. Chaos: под нагрузкой (гибрид-режим) docker compose stop redis — трафик жив, redis_errors растёт, Source = local-fallback; поднимите Redis — Source вернулся в hybrid:global.
  3. Фейловер: стенд master+replica+Sentinel (курс «Redis как брокер», М2) → make failover посреди теста точности → замерьте «дыру» пропущенного лимита (ожидаемо и ограниченно).
  4. Метрики: соберите 4 метрики из §6.2 в Prometheus, поставьте алерт на redis_errors > 0 за 5 минут.

Артефакт: конкурентный тест в CI + дашборд лимитера + заполненный чек-лист §6.4.


🧩 Quiz "Проверь себя"

1. Redis-мастер умер, Sentinel промоутил реплику. Что произойдёт с лимитом?
2. Какой тест доказывает кластерную точность лимитера?
3. Что в чек-листе продакшена важнее всего для лимитера тарифов (биллинг)?
4. Почему ключи лимитера не требуют hash tags в кластере Redis?

✅ Итоги модуля

ТемаГлавное
КластерОдиночный ключ = один слот; фейловер = краткий пропуск (осознанная цена); часы — now снаружи + delta<0
Метрикиexceeded{source}, redis_errors (алерт на fallback), p99 Allow
ТестКонкурентный тест «N процессов × M запросов ≤ burst+refill» — доказательство, в CI
Чек-листПолитика отказа, XFF, TTL, PII-маскирование, заголовки — решения, не настройки
КартаКэш/локи/лимиты/идемпотентность/очереди — разные цены отказа, разные политики

🎓 Итоги курса

Redis в Go-проекте — это набор механизмов с разными ценами отказа, а не одна зависимость. Курс дал:

  • Клиент как компонент (М1): таймауты, пул, политики отказа — решение принимается один раз в обёртке;
  • Кэш и локи (М2): singleflight против штампера, SET NX PX + токен, честные границы Redlock;
  • Rate limiting (М3–М5): алгоритмы и Token Bucket на Lua, входящий гибрид local+global с graceful degradation, исходящий throttle для внешних API с уважением Retry-After;
  • Эксплуатация (М6): фейловер, метрики, конкурентный тест точности, чек-лист продакшена.

Дальше по треку Redis: курс «Redis как брокер» — Streams, consumer groups, очереди и их эксплуатация. Вместе эти два курса закрывают карту Redis-применений: данные-в-памяти и движение-событий.