Опирается на правила:
R-CACHE-TTL-1…R-CACHE-TTL-4иR-CACHE-TTL-X1…R-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 / preferences | 15-30 мин | UserProfile, UserSettings |
| Feature flags | 30-60 сек | FeatureFlagSet, A/B test buckets |
| Configuration overrides | 60 сек | TenantConfig, runtime settings |
| Heavy aggregations | 5-10 мин | DailyReport, TopProducts, статистика |
| Money-related | 5-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 старого релиза.
Сценарий:
- Релиз v1.0 кеширует
UserProfile { id, name, email }. - Релиз v1.1 переименовывает
email→primaryEmail. - Старые 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 TTL | R-CACHE-TTL-X1 | explicit duration (5m, 30s) |
| TTL > 24 часов для бизнес-данных | R-CACHE-TTL-X2 | TTL ≤ 24h или version-suffix в cacheNames |
| Money без TTL | R-CACHE-TTL-X3 | TTL ≤ 30s + явный evict |
| TTL hard-coded в Java | R-CACHE-TTL-3 | application.yml + @ConfigurationProperties |
| Один TTL на все кеши | R-CACHE-TTL-1 | per-cache config |
| TTL без учёта характера данных | R-CACHE-TTL-2 | таблица типовых значений |
| Long TTL без invalidation event | R-CACHE-TTL-4 | short TTL либо invalidation надёжный |
Куда дальше
- Caching → раздел 4. TTL — нормативные формулировки.
- Конфигурация —
application.yml+@ConfigurationProperties. - Где кешируем — money-rules.
- Invalidation —
@CacheEvict, event-driven. - Паттерны — refresh-ahead для hot keys (TTL менее важен).
- Observability — мониторинг hit rate, понимание реального TTL.