Redis недоступен¶
Runbook: сервис бросает ошибки на Redis-вызовах, cache_errors_total
растёт, endpoint'ы fail-closed отдают 503. Эта страница — диагностика
«что именно не так с Redis и чего делать».
Как правильно проектировать сервис на случай такого инцидента —
../how-to/handle-redis-outage.md.
Reference по клиенту, TTL, naming — в
../conventions/caching.md.
Содержание¶
- Симптомы
- Быстрая проверка: это действительно Redis?
- 1. Pod не может дозвониться
- 2. Таймаут на команде
- 3. Pool исчерпан
- 4. Memory full / eviction storm
- 5. Cluster in-flight (для cluster-mode)
- 6. WRONGPASS / NOAUTH
- 7. READONLY (replica)
- Команды диагностики
- Порядок действий
- Что не делать
- Чеклист
- Связанные разделы
Симптомы¶
cache_errors_total{service=<name>}растёт.- В логах сервиса —
redis: connection refused,i/o timeout,pool exhausted,WRONGPASS,READONLY You can't write against a read only replica. - Endpoint'ы с fail-closed (idempotency-key, reset-password) отдают
- Endpoint'ы с fail-open работают медленнее (всё летит в БД).
/readyz503 на сервисах, где Redis включён в ready-checks.- Dashboard → p99 latency по affected endpoint'ам растёт (если fail- open, запрос уходит в БД).
Быстрая проверка: это действительно Redis?¶
Признак действительно Redis:
- Ошибки только на Redis-вызовах (
redis:в тексте ошибки). - БД/Kafka запросы проходят нормально.
redis-cli PINGне отвечает / таймаутит.
Если «кэш мимо» + при этом и БД медленная — скорее всего, БД
перегружена из-за fail-open cache, см.
db-slow-query.md. Тогда корень — Redis, а БД
только страдает от отсутствия кэша.
1. Pod не может дозвониться¶
Симптом. dial tcp: connection refused, i/o timeout на
DialTimeout, no such host при DNS-resolve.
Причины.
- Redis pod упал (k8s CrashLoopBackOff).
- Service DNS-имя изменилось / Kubernetes Service удалён.
- NetworkPolicy блокирует трафик pod → redis.
- Неверный
REDIS_ADDRв env (typo в config).
Проверка.
# DNS внутри pod'а сервиса
kubectl exec -it <service-pod> -- sh -c "nslookup $REDIS_HOST"
# TCP connectivity
kubectl exec -it <service-pod> -- sh -c "nc -zv $REDIS_HOST 6379"
# ручной ping из pod'а
kubectl exec -it <service-pod> -- sh -c "redis-cli -h $REDIS_HOST PING"
# состояние redis pod'а
kubectl get pod -l app=redis
kubectl describe pod -l app=redis
kubectl logs -f <redis-pod> --tail=100
Фикс.
CrashLoopBackOff→ читаем logs, фиксим (обычно memory limit недостаточен).- NetworkPolicy → эскалация к инфра-команде.
- Typo в env →
kubectl edit configmap <service>-config, rollout.
Обычно проблема не на стороне backend-сервиса. Мы ждём восстановления и на своей стороне проверяем, что fail-open работает как должен.
2. Таймаут на команде¶
Симптом. Connection устанавливается, но GET/SET/INCR
возвращает i/o timeout через ReadTimeout.
Причины.
- Medium Redis. CPU утилизирован > 90%, одна команда медленная.
- Blocking command. Кто-то запустил
KEYS */FLUSHDB/SMEMBERS <huge set>— блокирует single-threaded Redis. - Большой payload.
GETвозвращает 10MB — сетевая передача занимает большеReadTimeout. - Сеть. Packet loss между pod'ом сервиса и Redis.
Проверка.
# INFO stats + cpu
redis-cli INFO stats | grep -E "instantaneous_ops_per_sec|used_cpu"
redis-cli INFO commandstats | head -30
# SLOWLOG — какие команды тормозят
redis-cli SLOWLOG GET 20
# CLIENT LIST — что сейчас выполняется
redis-cli CLIENT LIST | head -20
# размеры ключей (sample)
redis-cli --bigkeys --i 0.01
SLOWLOG — главная диагностика. Покажет long-running команды с
временем и клиентом.
Фикс.
- Blocking command. Найти клиента (CLIENT LIST), kill'нуть через
CLIENT KILL ID <id>. Фикс в коде — не использоватьKEYS, заменить наSCAN. - Большой payload. Не храним объекты > 100KB в одном ключе. Разбивка на chunks или архитектурное решение (не в Redis).
- CPU 100%. Либо слишком много RPS на один instance (scale — см.
Redis cluster), либо сложные команды (
LUAscripts).
3. Pool исчерпан¶
Симптом. redis: connection pool timeout или ERR pool exhausted.
Метрика redis_pool_idle_connections = 0, redis_pool_active_connections
= PoolSize.
Причины.
- Handler задерживает команды (слишком медленный downstream в цепочке).
PoolSizeслишком мал для текущей нагрузки.- Goroutine leak держит соединения (см.
memory-leak.md). - Долгий
BLPOP/ subscribe занимает соединение навсегда.
Проверка.
// в сервисе можно экспортировать pool stats
stats := rdb.PoolStats()
log.Info("redis pool", "hits", stats.Hits, "misses", stats.Misses,
"timeouts", stats.Timeouts, "total", stats.TotalConns, "idle", stats.IdleConns)
Фикс.
- Увеличить
PoolSizeвUniversalOptions(× 2 как экспериментальный шаг). Default в go-redis — 10 ×GOMAXPROCS, что на 16-core pod'е даёт 160. Если этого мало — проверь, не утекают ли соединения. - Leak: искать в goroutine-profile.
BLPOPи подобные — изолировать в отдельный pool (go-redisсоздаёт отдельное соединение наSubscribe, но дляBLPOPнужно явно).
4. Memory full / eviction storm¶
Симптом. OOM command not allowed when used memory > 'maxmemory',
высокий evicted_keys rate, записи пропадают сразу после SET.
Причины.
- Redis упёрся в
maxmemory,maxmemory-policyне успевает evict'ить. - Огромный ключ занимает гигабайт.
- Утечка в application: ключи без TTL, бесконечные коллекции.
Проверка.
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|maxmemory_policy"
redis-cli INFO stats | grep evicted_keys
# топ-N по размеру
redis-cli --bigkeys
redis-cli MEMORY USAGE <key>
# список ключей без TTL — через SCAN, не KEYS
redis-cli --scan | while read k; do
[ "$(redis-cli TTL "$k")" = "-1" ] && echo "$k"
done | head -20
Фикс.
- Найти источник. Bigkey (фото в base64 в cache?), утечка (ключи
с
TTL=-1)? См.caching.md— бесконечный TTL запрещён. - Изменить
maxmemory-policy. Для cache —allkeys-lru. Если сейчасnoeviction— Redis отказывает запросам при переполнении. - Scale вертикально (больше RAM) или горизонтально (Redis cluster). Инфра-задача.
- Сократить TTL в сервисе временно, чтобы снизить нагрузку.
5. Cluster in-flight (для cluster-mode)¶
Симптом. MOVED 1234 redis-cluster-2:6379 в ответах, слоты
переезжают между nodes.
Причина. Cluster resharding — штатная операция, в прогрессе.
Клиент go-redis UniversalClient должен автоматически обрабатывать
MOVED/ASK через AutoRepair / ReadFromReplicas.
Фикс.
- В нормальном режиме — ничего, клиент сам догоняет.
- Если клиент не догоняет (старая версия библиотеки) — рестарт pod'а.
- Отключить частый
Subscribeна время resharding'а (subscribers наиболее чувствительны к node-переключениям).
6. WRONGPASS / NOAUTH¶
Симптом. WRONGPASS invalid username-password pair или NOAUTH
Authentication required.
Причины.
- Неверный пароль в config (ротация ключа была, env не обновлён).
- ACL правила изменились (user больше не существует / нет прав).
requirepassна Redis изменён, old-клиенты упали.
Проверка.
# проверить пароль в k8s secret
kubectl get secret <service>-redis-secret -o jsonpath='{.data.REDIS_PASSWORD}' | base64 -d
# ACL на redis
redis-cli ACL LIST
Фикс.
- Обновить
REDIS_PASSWORDв Secret,kubectl rollout restart deployment/<service>. - Координация с Redis-owner'ом при ротации — см.
../how-to/rotate-jwt-key.md(шаблон для key rotation применим и к Redis).
7. READONLY (replica)¶
Симптом. READONLY You can't write against a read only replica.
Причина. Клиент обращается с WRITE-командой к replica'е, а не к primary. Это происходит при:
- Failover — primary упал, replica ещё не promote'нут.
ReadFromReplicas: trueв клиенте применяется и к writes (конфигурационная ошибка).- DNS указывает на replica.
Фикс.
- Дождаться failover (обычно < 30s с Sentinel / Redis Cluster).
- Проверить client config:
MasterName(для Sentinel), addr list (для Cluster). Writes всегда должны идти на primary.
Команды диагностики¶
# общий health
redis-cli PING # PONG = жив
redis-cli INFO server # version, uptime, mode
redis-cli INFO replication # master/slave, connected_slaves
# нагрузка
redis-cli INFO stats
redis-cli INFO clients # сколько клиентов
redis-cli INFO commandstats # топ команд по calls/usec
# память
redis-cli INFO memory
redis-cli MEMORY STATS
redis-cli --bigkeys --i 0.01 # топ-10 big-ключей, с rate-limit
# slow log
redis-cli CONFIG GET slowlog-log-slower-than # default: 10000 µs
redis-cli SLOWLOG GET 20 # последние 20
redis-cli SLOWLOG RESET # сброс
# keyspace
redis-cli DBSIZE
redis-cli --scan --pattern "review:*" | wc -l # сколько review-ключей
# ACL
redis-cli ACL WHOAMI
redis-cli ACL LIST
# клиенты (убить зависший)
redis-cli CLIENT LIST
redis-cli CLIENT KILL ID <id>
# в pod'е сервиса
kubectl exec -it <service-pod> -- sh -c "redis-cli -h $REDIS_HOST PING"
kubectl logs <service-pod> | grep -i redis | tail -50
Порядок действий¶
- Подтверди, что это Redis. Ошибки содержат
redis:, БД/Kafka работают. PING. Пинг отвечает? Нет → §1 (networking / pod).INFO memory. OOM? → §4.SLOWLOG GET 20. Есть долгие команды? → §2.CLIENT LIST. Много connected, мало idle? → §3 (pool).INFO replication. На primary ли мы? → §7.- Если ничего — логи Redis pod'а:
kubectl logs <redis-pod>. Обычно там подсказка. - Пока разбираемся — отключи fail-closed endpoint'ы.
Если affected endpoint'ы критичны, временно переключи фичу на
degraded mode (через feature-flag или config), чтобы запросы не
давали 503. См.
../how-to/handle-redis-outage.md.
Что не делать¶
FLUSHDB/FLUSHALLв prod — уничтожает весь кэш, создаёт stampede на БД.KEYS *— блокирует Redis на секунды/минуты. ИспользуйSCAN.DEBUG SEGFAULT— роняет Redis намеренно. Не смешно.- Рестарт сервис-pod'ов «чтобы пересоединились» — без диагностики это ничего не даст, только размножит connection spike.
CONFIG SET maxmemory 0в прод — неограниченная память ест весь node, приведёт к OOM kill всего Redis.- Перезапуск Redis pod'а без координации. В cluster-mode это триггерит failover. Уведомить инфра-команду.
Чеклист¶
- Подтвердил: ошибки связаны с Redis (не БД, не Kafka).
-
PINGиз pod'а сервиса отвечает / не отвечает — записал. -
INFO memoryпроверен — нет OOM. -
SLOWLOG GET 20— нет долгих команд / есть и виновник найден. -
CLIENT LIST— pool не забит в нормальном режиме. - Application логи подтверждают ту же картину (timeout / refused / pool exhausted).
- Fail-open endpoint'ы работают через БД (пусть и медленнее).
- Fail-closed endpoint'ы отдают 503, не 500.
- Incident log с root cause написан.
- Post-incident: усилить monitoring на тот класс проблем, что случился.
Связанные разделы¶
../how-to/handle-redis-outage.md— как сервис должен переживать: fail-open/closed per operation.../conventions/caching.md— cache-aside, TTL, eviction policies.../conventions/data-retention.md— retention в Redis, причины memory-роста.../patterns/idempotent-consumer.md— dedup при недоступном Redis.db-slow-query.md— если БД тормозит после Redis-outage (fail-open нагрузил pool).memory-leak.md— pool leak, pprof.../how-to/profile-service.md— goroutine snapshot при pool exhaustion.