Опирается на правила: R-CACHE-TTL-1R-CACHE-TTL-4 и R-CACHE-TTL-X1R-CACHE-TTL-X3 из Caching Style Guide → раздел 4. TTL.

Важно знать

  • Каждый cache имеет explicit TTL. Никаких infinite-кешей.
  • Типовые значения по характеру: static reference — часы; user profile — 15-30 мин; feature flags — 30-60 сек; heavy aggregations — 5-10 мин; money — 5-30 сек.
  • TTL — в application.yml, не hard-coded в Java. SRE поднимает/понижает без redeploy.
  • Если есть естественный invalidation event — TTL длиннее, invalidation делает основную работу.
  • Duration.ZERO = infinite в Spring — Redis при max-memory eviction по LRU без вашего контроля.
  • TTL > 24 часов — кеш переживает deploy с устаревшей структурой DTO → десериализация ломается на проде.
  • Money без TTL — главный антипаттерн: списали, баланс в кеше старый → инцидент.

TTL — единственная защита от ситуации «invalidation забыл». Если invalidation правильный, TTL — backup; если нет — TTL единственное, что не даёт кешу жить вечно. UCP формулирует значения так, чтобы стоимость stale-data не превышала выгоду кеша.

Explicit TTL на каждом кеше

R-CACHE-TTL-1: ноль infinite-кешей в проде.

cache:
  caches:
    user-profiles:
      ttl: 15m
    currencies:
      ttl: 6h
    feature-flags:
      ttl: 60s
    user-balances:
      ttl: 30s
    order-summaries:
      ttl: 5m
    top-products:
      ttl: 10m

Бэкграунд:

@Bean
RedisCacheManager cacheManager(RedisConnectionFactory cf, ObjectMapper mapper, CacheSettings settings) {
    var defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
        .serializeValuesWith(SerializationPair.fromSerializer(
            new GenericJackson2JsonRedisSerializer(mapper)))
        .entryTtl(Duration.ofMinutes(15))
        .disableCachingNullValues();

    var perCache = settings.caches().entrySet().stream()
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            e -> defaultConfig.entryTtl(e.getValue().ttl())
        ));

    return RedisCacheManager.builder(cf)
        .cacheDefaults(defaultConfig)
        .withInitialCacheConfigurations(perCache)
        .build();
}

Дефолт 15m — для всех не-named кешей. Per-cache config переопределяет.

Типовые значения по характеру данных

R-CACHE-TTL-2: ориентируемся на природу данных, не на «короче-лучше».

Тип данныхTTLПример
Static referenceчасыcurrencies, countries, timezones, доступные валюты
User profile / preferences15-30 минUserProfile, UserSettings
Feature flags30-60 секFeatureFlagSet, A/B test buckets
Configuration overrides60 секTenantConfig, runtime settings
Heavy aggregations5-10 минDailyReport, TopProducts, статистика
Money-related5-30 секUserBalance (только с явной evict-стратегией)
OAuth/JWT keys (JWK)5 минJWK Set — встроено в Spring Security (AUTH-5)

Логика:

  • Short TTL (секунды) — данные часто меняются, stale = ощутимая проблема.
  • Medium TTL (минуты) — изменения редкие, но возможные; пользователь стерпит задержку 15 минут.
  • Long TTL (часы) — справочники, практически не меняющиеся; редкие изменения требуют ручного evict.

TTL в application.yml

R-CACHE-TTL-3: hard-coded Duration.ofMinutes(15) в Java — антипаттерн.

// ПЛОХО
@Cacheable(cacheNames = "user-profiles")
@CacheConfig(...)
// TTL зашит в @Bean RedisCacheManager
.entryTtl(Duration.ofMinutes(15))

Когда SRE замечает «у нас инциденты из-за stale user profile» — нужен redeploy, чтобы сменить с 15m на 5m. Это часы.

cache:
  caches:
    user-profiles:
      ttl: 5m

Один config-map update + rolling restart pod-ов (или Spring Cloud Config refresh) — минуты.

TTL и invalidation — два слоя защиты

R-CACHE-TTL-4: если у данных есть естественный invalidation event — TTL может быть длиннее.

Сценарий 1 — есть event:

OrderConfirmed event → @EventListener evict("order-summaries", orderId)

TTL для order-summaries — можно 30 минут. Invalidation срабатывает быстро, TTL — резерв на случай пропущенного события.

Сценарий 2 — нет event (например, внешний справочник, изменения вне нашего контроля):

Currencies в БД меняются через ETL ночью → нет события

TTL для currencies — 6 часов. Это компромисс: stale до 6 часов терпимо для отображения курса валют.

Главный принцип: invalidation первичен, TTL вторичен. Если invalidation надёжен — TTL может быть длиннее. Если нет — TTL короче, чтобы ограничить ущерб.

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

Duration.ZERO или infinite

R-CACHE-TTL-X1: Spring трактует Duration.ZERO как «no TTL» = forever.

// ОПАСНО
.entryTtl(Duration.ZERO)
.entryTtl(Duration.ofDays(Long.MAX_VALUE))

Что происходит:

  • Redis заполняется бесконечно растущим кешем.
  • При достижении maxmemory срабатывает eviction policy (LRU/LFU по умолчанию).
  • Вы не контролируете, что именно evict-ится.
  • В прод может evict-нуться «критичный» ключ, а stale-данные остаться.

Всегда Duration.ofMinutes(...) или Duration.ofHours(...).

TTL > 24 часов для бизнес-данных

R-CACHE-TTL-X2: сервис деплоится чаще раза в сутки. Длинный TTL переживает релиз → в кеше структуры DTO старого релиза.

Сценарий:

  1. Релиз v1.0 кеширует UserProfile { id, name, email }.
  2. Релиз v1.1 переименовывает emailprimaryEmail.
  3. Старые pods исчезли, новые читают кеш v1.0 → Jackson DeserializationException.

Защита:

  • TTL ≤ 24 часов — после релиза остаётся максимум день старого кеша.
  • Или явный version-suffix в cacheNames (user-profiles-v2) — при breaking change меняем имя кеша.

Money-кеш без TTL

R-CACHE-TTL-X3: см. R-CACHE-WHERE-X3 в Где кешируем.

Money всегда:

  • TTL 5-30 секунд.
  • @CacheEvict на каждой write-операции.
  • Для критичных flows — cache.evict() перед чтением.

«Кешируем баланс на 5 минут потому что прод медленный» — путь к инциденту. Лучше оптимизировать запрос к БД (индекс, read-replica), чем терпеть stale баланс.

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

АнтипаттернПравилоЧто взамен
Duration.ZERO / infinite TTLR-CACHE-TTL-X1explicit duration (5m, 30s)
TTL > 24 часов для бизнес-данныхR-CACHE-TTL-X2TTL ≤ 24h или version-suffix в cacheNames
Money без TTLR-CACHE-TTL-X3TTL ≤ 30s + явный evict
TTL hard-coded в JavaR-CACHE-TTL-3application.yml + @ConfigurationProperties
Один TTL на все кешиR-CACHE-TTL-1per-cache config
TTL без учёта характера данныхR-CACHE-TTL-2таблица типовых значений
Long TTL без invalidation eventR-CACHE-TTL-4short TTL либо invalidation надёжный

Куда дальше

  • Caching → раздел 4. TTL — нормативные формулировки.
  • Конфигурация — application.yml + @ConfigurationProperties.
  • Где кешируем — money-rules.
  • Invalidation — @CacheEvict, event-driven.
  • Паттерны — refresh-ahead для hot keys (TTL менее важен).
  • Observability — мониторинг hit rate, понимание реального TTL.