Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Кэш-инвалидация: стратегии

Кэш-инвалидация: удаление и обновление записей кэша

Стратегии инвалидации кэша: удаление против обновления, окно несогласованности, групповая инвалидация и защита от массового промаха.

Прямой ответ

Инвалидация кэша удаляет либо обновляет запись после изменения данных, поэтому пользователь не видит прежнее значение после записи.

Стратегия Что выполняется после записи
Удаление записи Запись удаляется из кэша
Обновление записи Запись перезаписывается новым значением
Истечение срока Запись удаляется по истечении срока жизни
Групповая инвалидация Удаляется группа связанных записей
def update_user(user_id: int, data: dict):
    db.update_user(user_id, data)
    redis.delete(f"user:{user_id}")
    redis.delete(f"user_list:{data['tenant_id']}")

Удаление записи после записи в источник оставляет кэш пустым для этого ключа, поэтому следующее чтение получает актуальное значение из источника. О стратегиях кэширования — в статье про кэширование данных.

Удаление против обновления записи

Удаление и обновление записи дают разное окно несогласованности, поэтому выбор определяется порядком операций.

Удаление записи. Запись удаляется из кэша, поэтому следующее чтение обращается к источнику и получает актуальное значение.

Обновление записи. Запись перезаписывается новым значением, поэтому чтение получает новое значение без обращения к источнику.

Порядок операций. Запись в источник до инвалидации сокращает окно несогласованности, поэтому инвалидация после записи предпочтительнее.

Практическое следствие: инвалидация до записи в источник оставляет окно, в котором другое чтение возвращает прежнее значение и снова помещает его в кэш.

Отдельный аспект — обновление записи при параллельных записях. Две записи, выполняемые одновременно, могут поместить в кэш значения в порядке, не совпадающем с порядком записи в источник, поэтому кэш содержит прежнее значение. Удаление записи вместо обновления устраняет такую гонку.

Групповая инвалидация и связанные данные

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

Связанные ключи. Изменение записи затрагивает списки и агрегаты, содержащие эту запись, поэтому инвалидация охватывает все связанные ключи.

Групповая инвалидация. Ключи объединяются по признаку, поэтому удаление группы выполняется одной операцией.

Версия данных. Ключ кэша включает версию данных, поэтому изменение версии делает прежние записи недоступными без явного удаления.

Практическое следствие: инвалидация только записи без связанных списков приводит к тому, что список продолжает показывать прежнее значение.

Массовый промах и защита

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

Массовый промах. Удаление группы записей приводит к одновременным обращениям к источнику, поэтому нагрузка на источник растёт.

Блокировка обновления. Одно чтение выполняет обновление кэша, остальные ожидают результат, поэтому источник получает одно обращение.

Разброс срока жизни. Срок жизни записей различается, поэтому истечение не происходит одновременно и пик нагрузки не возникает.

Практическое следствие: массовая инвалидация без защиты приводит к тому, что источник получает число обращений, равное числу промахов.

О защите от массового промаха — в статье про cache stampede; о структурах Redis для кэша — в статье про структуры Redis.

Практика: настройка инвалидации кэша

1. Инвалидируйте запись после записи в источник. Окно несогласованности сокращается, поэтому прежнее значение не возвращается.

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

3. Охватывайте связанные ключи при инвалидации. Списки и агрегаты, содержащие запись, инвалидируются вместе с записью.

4. Используйте версию данных в ключе кэша. Изменение версии делает прежние записи недоступными без явного удаления.

5. Защищайте от массового промаха. Блокировка обновления оставляет одно обращение к источнику вместо числа промахов.

6. Разбрасывайте срок жизни записей. Истечение не происходит одновременно, поэтому пик нагрузки не возникает.

7. Проверяйте поведение при недоступности кэша. Инвалидация не выполняется, поэтому чтение обращается к источнику.

Рекомендации:

  1. Не инвалидируйте кэш до записи в источник. Окно несогласованности позволяет другому чтению вернуть прежнее значение в кэш.
  2. Учитывайте связанные ключи при изменении данных. Инвалидация только записи оставляет списки с прежним значением.
  3. Сопоставляйте стратегию с параллелизмом записи. Параллельные записи требуют удаления записи вместо обновления.
  4. Фиксируйте долю промахов в метриках. Рост промахов после инвалидации указывает на массовое истечение.
  5. Пересматриваем набор ключей при изменении структуры данных. Новый агрегат требует включения в набор инвалидируемых ключей.

Практический сценарий: список показывал удалённую запись

Контекст: запись была удалена, но список продолжал показывать её, поэтому пользователь видел несуществующие данные.

  1. Диагностика. Проверка показала, что при удалении записи инвалидировался только ключ самой записи. Ключ списка, содержащий эту запись, не инвалидировался, поэтому список возвращал прежнее значение из кэша.
  2. Причина. Набор инвалидируемых ключей не охватывал связанные списки, поэтому изменение не отражалось в агрегированных данных.
  3. Действия. (а) Определили полный набор ключей, зависящих от записи, включая списки и агрегаты; (б) добавили инвалидацию всех связанных ключей при изменении записи; (в) перешли на удаление записи вместо обновления, чтобы исключить гонку при параллельных записях; (г) добавили разброс срока жизни, чтобы записи не истекали одновременно; (д) настроили метрику доли промахов, чтобы массовое истечение обнаруживалось.
  4. Результат. Список отражает изменение немедленно, поэтому удалённая запись не отображается.

Вывод: изменение данных затрагивает несколько ключей кэша, поэтому инвалидация только записи оставляет связанные списки с прежним значением. Полный набор инвалидируемых ключей обеспечивает согласованность кэша с источником.

FAQ

Что выбрать: удаление или обновление записи кэша? Удаление, поскольку при параллельных записях обновление может поместить в кэш прежнее значение из-за порядка операций.

Когда выполнять инвалидацию относительно записи в источник? После записи в источник. Инвалидация до записи оставляет окно, в котором другое чтение возвращает прежнее значение в кэш.

Почему список показывает прежнее значение после изменения записи? Ключ списка не входит в набор инвалидируемых ключей, поэтому агрегированные данные не обновляются.

Как защититься от массового промаха после инвалидации? Блокировкой обновления и разбросом срока жизни. Одно чтение обновляет кэш, остальные ожидают результат, поэтому источник получает одно обращение.

Источники: Redis eviction, AWS Caching Best Practices, PostgreSQL query tuning — ссылки в шапке.