Инвалидация кэша
«В компьютерных науках есть только две сложные проблемы: инвалидация кэша и именование» — Фил Карлтон. Этот урок разбирает стратегии, чтобы кэш всегда содержал актуальные данные.
Почему это важно: Устаревший кэш — это баги, которые сложно воспроизвести. Пользователь видит старую цену, старый баланс или чужие данные. Правильная инвалидация — разница между надёжным и непредсказуемым кэшем.
Главная идея
Инвалидация — это удаление или обновление записи в кэше при изменении исходных данных. Основные стратегии: TTL (автоматическое истечение), event-driven (удаление при записи), version-based (ключ содержит версию).
Как это выглядит на практике
- Администратор меняет цену товара в панели управления
- Контроллер сохраняет новую цену в БД
- After-save callback удаляет кэш товара: REDIS.del('product:42')
- Callback также удаляет кэш каталога: REDIS.del('catalog:page:1')
- Следующий запрос на товар 42 — cache miss, свежие данные из БД
- Каталог тоже обновляется при следующем запросе с новой ценой
Примеры кода
Стратегии инвалидации
class Product < ApplicationRecord
# 1. Event-driven: удаление при изменении
after_save :invalidate_cache
after_destroy :invalidate_cache
private
def invalidate_cache
# Удаляем кэш самого товара
Rails.cache.delete("product:\#{id}")
# Удаляем кэш каталога по паттерну
Rails.cache.delete_matched("catalog:*")
# Удаляем кэш категории
Rails.cache.delete("category:\#{category_id}:products")
end
end
# 2. Version-based: ключ содержит версию
def cached_product(product)
key = "product:\#{product.id}:v\#{product.updated_at.to_i}"
Rails.cache.fetch(key, expires_in: 1.hour) do
ProductSerializer.new(product).as_json
end
end
# Старая версия автоматически вытесняется через LRU
Каскадная инвалидация с тегами
# Rails cache tags (с Redis store)
class ProductsController < ApplicationController
def index
@products = Rails.cache.fetch(
'catalog:all',
expires_in: 10.minutes,
tags: ['products'] # тег для групповой инвалидации
) do
Product.published.includes(:category).to_a
end
end
end
class Product < ApplicationRecord
after_save do
# Удалить ВСЕ записи с тегом 'products' одним вызовом
Rails.cache.delete_matched_tags('products')
end
end
Что происходит под капотом
- TTL — простейшая стратегия: данные устаревают гарантированно, но могут быть stale до истечения
- Event-driven — точная, но хрупкая: забыли callback — получили stale data
- Version-based — изящная: новая версия = новый ключ, старые вытесняются через LRU
- delete_matched с паттерном (catalog:*) может быть медленным при большом количестве ключей
- При каскадных связях (товар → категория → каталог) инвалидация быстро усложняется
Типичные ошибки и заблуждения
- «TTL достаточно для всего» — для критичных данных (цена, баланс) пользователь не должен видеть устаревшие данные даже 1 секунду
- «Можно обновить кэш вместо удаления» — обновление создаёт race condition, если два процесса обновляют одновременно
- «Cache tag — серебряная пуля» — массовая инвалидация по тегу может обнулить весь кэш и вызвать cold start
Ключевые выводы
- TTL — базовая защита от бесконечно устаревшего кэша, устанавливайте всегда
- Event-driven инвалидация (after_save/after_destroy) — точная, но требует дисциплины
- DEL > SET при инвалидации: удаление безопаснее обновления из-за race conditions
Термины урока
Связь с работой backend-разработчика
Используйте TTL как safety net и event-driven инвалидацию для критичных данных. Не увлекайтесь каскадной инвалидацией — лучше кэшировать мелкие кусочки и инвалидировать точечно, чем кэшировать большие блоки и инвалидировать всё разом.
Мини-разбор реальной ситуации
Маркетплейс кэшировал всю страницу каталога одним ключом. При изменении любого товара кэш сбрасывался целиком, вызывая каскад запросов к БД. Решение: кэшировать каждый товар отдельно (product:42) и собирать каталог из маленьких кэшей. Теперь изменение одного товара инвалидирует только один ключ.
Что запомнить
- TTL + event-driven инвалидация = надёжная комбинация для большинства случаев
- Удаляйте (DEL), а не обновляйте (SET) — это безопаснее при конкурентном доступе
- Кэшируйте мелкие единицы — инвалидация будет точнее и дешевле
Итог
Инвалидация кэша — самая сложная часть кэширования. Комбинация TTL (safety net) + event-driven удаление (точность) покрывает большинство сценариев. Кэшируйте мелко, инвалидируйте точечно, всегда думайте о race conditions.
Комментарии к уроку
Войдите, чтобы оставить комментарий.
Пока нет комментариев — будьте первым.