# 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"`. ## Суть ```go // main.go:119 failOpen := cfg.AppEnv != "production" ``` Когда Redis недоступен: - **Fail-open** (dev): запросы проходят без rate limiting. Сервис жив, но уязвим. - **Fail-closed** (production): rate limiter возвращает 503. Сервис недоступен, но защищён. ```mermaid 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) ```go // 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) ```go // 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 для всех эндпоинтов) ```go // Пример: 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