Модуль 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 by | paired с 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) | М2 | fail-open: медленнее, но жив |
| Распределённые локи | М2 | fail-close: работу не берём |
| Rate limit входящий (гибрид) | М3–М4 | fail-open (защита) / fail-close (тарифы) |
| Rate limit исходящий (внешние API) | М5 | fail-open (вызвать) / fail-close (дорого превышать) |
| Очереди и потоки событий | курс «Redis как брокер» | своя дисциплина (dual write, DLQ) |
| Идемпотентность (метки дедупа) | М2 и брокер-курс | fail-close на записи, TTL ≥ retention |
Один Redis — четыре механизма с разными ценами отказа. Именно различение этих цен (а не «Redis упал») и есть инженерная зрелость, которую проверяет этот курс.
💻 Практика
- Конкурентный тест точности: 8 процессов × 8 горутин, свой ключ каждой; таблица «пропущено/отклонено/ожидание»; повторите с наивным GET/SET вместо Lua — увидите превышение.
- Chaos: под нагрузкой (гибрид-режим)
docker compose stop redis— трафик жив,redis_errorsрастёт, Source = local-fallback; поднимите Redis — Source вернулся в hybrid:global. - Фейловер: стенд master+replica+Sentinel (курс «Redis как брокер»,
М2) →
make failoverпосреди теста точности → замерьте «дыру» пропущенного лимита (ожидаемо и ограниченно). - Метрики: соберите 4 метрики из §6.2 в Prometheus, поставьте алерт на redis_errors > 0 за 5 минут.
Артефакт: конкурентный тест в CI + дашборд лимитера + заполненный чек-лист §6.4.
🧩 Quiz "Проверь себя"
✅ Итоги модуля
| Тема | Главное |
|---|---|
| Кластер | Одиночный ключ = один слот; фейловер = краткий пропуск (осознанная цена); часы — 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-применений: данные-в-памяти и движение-событий.