Redis как кэш: TTL, eviction, стратегии cache-aside, write-through

↑ 4.1 Redis и MongoDB · ← Предыдущая · → Следующая

Желательно~4 мин чтенияУровень: middle

Зачем это на собесе

Кэширование — главный сценарий Redis. Спрашивают стратегии, инвалидацию и проблемы кэша (stampede, penetration).

Стратегии

СтратегияСхемаПлюсы / минусы
Cache-aside (lazy loading)приложение читает кэш; при промахе идёт в БД и кладёт в кэшпросто, кэшируется только нужное; первое обращение медленное, возможна устарелость
Read-throughкэш сам загружает данные из БДприложение проще; нужна интеграция
Write-throughзапись идёт в кэш и синхронно в БДсогласованность; выше задержка записи
Write-behindзапись в кэш, в БД — асинхронно пачкамибыстро; риск потери при сбое
Refresh-aheadобновление до истечения TTLнет холодных промахов; сложнее
public async Task<Product?> GetAsync(long id)
{
    var key = $"product:{id}";
    var cached = await _db.StringGetAsync(key);
    if (cached.HasValue) return JsonSerializer.Deserialize<Product>(cached!);
 
    var product = await _repo.FindAsync(id);
    if (product is not null)
        await _db.StringSetAsync(key, JsonSerializer.Serialize(product), TimeSpan.FromMinutes(10) + TimeSpan.FromSeconds(Random.Shared.Next(60)));
    return product;
}
 
public async Task UpdateAsync(Product p)
{
    await _repo.SaveAsync(p);
    await _db.KeyDeleteAsync($"product:{p.Id}");   // инвалидация
}

Инвалидация: удалять ключ при изменении (проще и безопаснее, чем обновлять значение в кэше).

TTL

Всегда задавайте срок жизни: защита от вечно устаревших данных и переполнения. Добавляйте разброс (jitter), чтобы ключи не истекали одновременно.

Политики вытеснения (maxmemory-policy)

При достижении maxmemory Redis вытесняет ключи:

ПолитикаПоведение
noevictionошибка при записи (по умолчанию)
allkeys-lruвытеснить наименее недавно использованные из всех
allkeys-lfuнаименее часто используемые
volatile-lru / volatile-lfu / volatile-ttlтолько ключи с TTL
allkeys-random, volatile-randomслучайные

Для чистого кэша — allkeys-lru или allkeys-lfu. LRU/LFU приближённые (выборка ключей).

Проблемы кэша

ПроблемаОписаниеРешения
Cache stampede (dog-pile)ключ истёк, тысячи запросов одновременно идут в БДблокировка на загрузку (single flight), jitter TTL, refresh-ahead, stale-while-revalidate
Cache penetrationзапросы несуществующих ключей всегда мимо кэшакэшировать «пустой» результат с коротким TTL, Bloom-фильтр
Cache avalancheмассовое одновременное истечение или падение кэшаjitter, репликация, деградация, лимиты на БД
Hot keyодин ключ с огромной нагрузкойлокальный кэш (L1), репликация ключа, шардирование по суффиксу
Big keyогромное значениеразбивать, сжимать
Устаревшие данныерасхождение с БДинвалидация, короткий TTL, событийное обновление

Двухуровневый кэш: L1 в памяти процесса (IMemoryCache) + L2 Redis; инвалидация L1 через pub/sub. В .NET: IDistributedCache, HybridCache (single-flight и L1/L2 из коробки).

Что кэшировать

Читаемое часто и редко изменяемое, дорогое в вычислении, справочники, результаты внешних вызовов, сессии, агрегаты. Не кэшируйте то, что требует строгой консистентности в реальном времени.

Ключи и сериализация

Версионирование в ключе: product:v2:42 (смена формата без ручной очистки); компактные форматы (MessagePack, protobuf) вместо JSON для больших объёмов; сжатие.

Метрики

Hit ratio (keyspace_hits, keyspace_misses), evictions, использование памяти, задержки (SLOWLOG, LATENCY).

Вопросы с ответами