Опирается на правила: R-CACHE-OBS-1R-CACHE-OBS-4 и R-CACHE-OBS-X1 из Caching Style Guide → раздел 8. Observability.

Важно знать

  • Spring Cache metrics автоматически через Micrometer: cache_gets_total{cache,result}, cache_puts_total, cache_evictions_total, cache_size.
  • Hit rate = hits / (hits + misses) — основная метрика здоровья.
  • Алерт при hit rate < 70% для долго существующих кешей — либо неподходящий TTL, либо слишком частые invalidation.
  • Eviction логируется на DEBUG с key. INFO/WARN — будет шумно.
  • Redis-side метрики отдельно через Redis Exporter для Prometheus.
  • Отключение management.metrics.enable.cache=false — антипаттерн: без метрик не видно, что кеш бесполезен.

Кеш без observability — это «надеемся, что работает». Hit rate 5% означает «потратили инфру на Redis, эффект нулевой» — а узнаёшь об этом только когда вспомнишь проверить.

Автоматический экспорт через Micrometer

R-CACHE-OBS-1: Spring Cache + Actuator экспортирует метрики без custom-кода.

МетрикаЧто показывает
cache_gets_total{cache, result="hit"}Сколько hits в этом кеше
cache_gets_total{cache, result="miss"}Сколько misses
cache_puts_total{cache}Сколько puts (load from DB + put)
cache_evictions_total{cache}Сколько evictions
cache_size{cache}Размер кеша (для bounded)

Включение метрик в application.yml:

management:
  metrics:
    enable:
      cache: true
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus

management.metrics.enable.cache: true — дефолт, но явно прописываем чтобы не отключить случайно.

В Prometheus метрики появляются автоматически с тегами service, env, version, cache.

Hit rate — главная метрика

R-CACHE-OBS-2: расчёт hit rate в Prometheus.

sum by (cache) (rate(cache_gets_total{result="hit"}[5m]))
  /
sum by (cache) (rate(cache_gets_total[5m]))

Дашборд: hit rate per cache, исторический trend.

Алерт:

- alert: CacheHitRateLow
  expr: |
    (
      sum by (cache, service) (rate(cache_gets_total{result="hit"}[1h]))
      /
      sum by (cache, service) (rate(cache_gets_total[1h]))
    ) < 0.7
  for: 30m
  annotations:
    summary: "Cache {{ $labels.cache }} hit rate < 70%"
    runbook: https://runbooks.internal/cache-low-hit-rate

Hit rate < 70% значит одну из проблем:

  1. TTL слишком короткий. Данные истекают до того, как клиенты их прочитали повторно. Увеличить TTL.
  2. TTL слишком длинный + частая invalidation. Каждый write evict-ит — кеш пустой большую часть времени. Перепроектировать паттерн.
  3. Ключи unbounded. Каждый запрос — уникальный ключ (order-search с детальными фильтрами). Кеш не помогает; убрать или сделать менее гранулярным.
  4. Кеш для редко читаемых данных. Если ratio read/write < 10:1 — кеш приносит больше работы, чем экономии. Отключить.

Runbook должен включать процедуру диагностики через cache_gets_total, cache_puts_total, cache_evictions_total — чтобы оператор быстро определил, что именно ломается.

Eviction — DEBUG, не INFO

R-CACHE-OBS-3: каждый @CacheEvict потенциально срабатывает на каждом write. Логирование на INFO даст 100 строк/секунду в высоконагруженном сервисе.

@Component
@Slf4j
public class CacheEvictionLogger {

    @EventListener
    public void onCacheEvent(CacheEvent event) {
        if (event.type() == CacheEvent.Type.EVICTION) {
            log.debug("Cache evict: cache={} key={}", event.cacheName(), event.key());
        }
    }
}

DEBUG включается per-environment при инциденте, когда нужно понять «инвалидировался ли кеш после write». На проде по умолчанию выключен.

Redis-side метрики

R-CACHE-OBS-4: Spring Cache видит только Java-side. Redis-side состояние (memory pressure, cluster health, replication lag) мониторим отдельно через Redis Exporter.

МетрикаЧто показывает
redis_upДоступность Redis
redis_memory_used_bytesИспользованная память
redis_memory_max_bytesЛимит памяти (maxmemory)
redis_keys_total{db}Количество ключей
redis_evicted_keys_totalСколько ключей evicted Redis-ом по LRU/LFU
redis_cluster_stateHealth cluster mode
redis_master_replication_lag_secondsReplication delay

Алерт на redis_memory_used / redis_memory_max > 0.9 — близко к eviction. Алерт на redis_evicted_keys_total rate > 0 для не-eviction политики (noeviction) — что-то не так.

Что запрещено

Отключение Spring Cache metrics

R-CACHE-OBS-X1: management.metrics.enable.cache: false.

Сценарий: команда подключила Redis, написала @Cacheable, всё работает локально. На проде через месяц SRE замечает, что Redis не растёт. Открывает Grafana — метрик нет. Включает — hit rate 0%. Оказывается, @EnableCaching без CacheManager-бина → NoOpCacheManager (см. Конфигурация).

Метрики — единственная защита от silent fail. Не отключаем.

Что запрещено — таблица

АнтипаттернПравилоЧто взамен
management.metrics.enable.cache: falseR-CACHE-OBS-X1оставить включёнными
Eviction на INFOR-CACHE-OBS-3DEBUG
Нет алерта на hit rate < 70%R-CACHE-OBS-2алерт for: 30m с runbook
Только Java-side метрики, без Redis ExporterR-CACHE-OBS-4Redis-side тоже
Кеш без cache тега в метрикахR-CACHE-OBS-1per-cache именование (auto через Spring)
Hit rate измеряется avg, не rateR-CACHE-OBS-2rate(...)[5m]

Куда дальше

  • Caching → раздел 8. Observability — нормативные формулировки.
  • Observability → Metrics — стандарт service/env/version теги.
  • Observability → SLO и алерты — runbook для cache hit rate.
  • Конфигурация — @EnableCaching без CacheManager → no metrics.
  • TTL — низкий hit rate часто из-за неправильного TTL.
  • Где кешируем — низкий hit rate часто из-за wrong cacheable candidate.