atomic-redis-rate-limiter-failopen-failclosed.md 3.5 KB

Fail-open vs Fail-closed в rate limiter: trade-off безопасности

Контекст: В backend/cmd/api/main.go:119 выбор между fail-open и fail-closed для Redis rate limiter определяется окружением: failOpen := cfg.AppEnv != "production".

Суть

// main.go:119
failOpen := cfg.AppEnv != "production"

Когда Redis недоступен:

  • Fail-open (dev): запросы проходят без rate limiting. Сервис жив, но уязвим.
  • Fail-closed (production): rate limiter возвращает 503. Сервис недоступен, но защищён.

    graph TD
    A[Redis timeout / connection error] --> B{failOpen?}
    B -->|Yes - dev| C[In-Memory fallback]
    B -->|No - production| D[503 Service Unavailable]
    C --> E[Rate limiting weaker]
    D --> F[Service blocked]
    

Trade-off

Fail-open (dev)

// ratelimit_redis.go:61-64
if r.failOpen {
    next.ServeHTTP(w, req) // пропускаем запрос
    return
}

Плюсы:

  • Разработка не блокируется недоступностью Redis
  • Можно тестировать без Redis
  • Graceful degradation

Минусы:

  • Без Redis rate limiting работает на in-memory (сбрасывается при рестарте)
  • В кластере каждый инстанс имеет свой счётчик
  • Потенциальный abuse при падении Redis

Fail-closed (production)

// ratelimit_redis.go:66-69
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte(`{"error":"rate limiter unavailable"}`))

Плюсы:

  • Полная защита от abuse
  • Redis — SPoF не влияет на безопасность
  • Abuse-атака не может обойти rate limiter

Минусы:

  • Redis — Single Point of Failure
  • При падении Redis весь API (кроме /health) недоступен
  • Нужен Redis Sentinel/Cluster для HA

Рекомендации для прода

  1. Перейти на Redis Sentinel или Cluster — устранить SPoF
  2. Добавить alarm при падении Redis (например, Prometheus + Alertmanager)
  3. Рассмотреть грейсфул timeout: 100ms timeout на Redis, после чего in-memory fallback, но с пониженными лимитами (например, 5 req/min вместо 60)
  4. Вариант: fallback на in-memory с более строгими лимитами в production (например, 10 req/min для всех эндпоинтов)

    // Пример: fallback со строгими лимитами
    if r.failOpen {
    strictLimiter.Allow() // более строгий лимит для production fallback
    next.ServeHTTP(w, req)
    return
    }
    

Что уже сделано

  • Redis health check при старте (Ping + WarnContext)
  • 100ms timeout на Redis запросы
  • Логирование при падении Redis
  • Выбор стратегии через AppEnv

Связанные заметки

  • [[backend-rate-limiting]] — общая архитектура rate limiting
  • [[decision-rate-limiter-redis]] — почему выбрали Redis
  • [[architecture-overview]] — таблица P2 (CSP)

Источник

Code review PhotoPlaces 2026-06. main.go:119.

Теги

#redis #rate-limiting #production #availability #security #architecture