Инвалидация кэша и гонка позднего заполнения

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

Проследите за двумя запросами

Предположим, PostgreSQL хранит ревизию 0, а приложение использует чтение cache-aside. Такая последовательность возможна, даже если записывающий сеанс инвалидирует кэш после коммита:

Шаг Читатель A Записывающий сеанс B
1 Не находит ключ в кэше и читает ревизию 0  
2 Приостанавливается перед сохранением результата Обновляет строку до ревизии 1
3   Фиксирует транзакцию и удаляет ключ кэша
4 Публикует ранее прочитанную ревизию 0  
5 Следующий запрос читает устаревшее значение из кэша  

TTL может ограничить время, в течение которого это значение остаётся допустимым. Но он не делает шаг 4 корректным. Перенос удаления до коммита создаёт другой интервал, в котором читатель может снова заполнить кэш старым зафиксированным состоянием базы данных. См. руководство по PostgreSQL и Redis, где описана граница между cache-aside приложения и локальным кэшем строк базы данных.

Проверяйте публикацию вместе с поиском

Заполнению кэша нужны подтверждения того, что его результат всё ещё допустим в момент публикации. В pg_local_cache 2.0 записи ограждают затронутые ключи или отношения; заполнения несут сведения о поколении, а положительные записи кэша — сведения о видимости кортежа. Изменившееся поколение может отклонить устаревшее заполнение. Чтение, которому небезопасно использовать запись, переходит к PostgreSQL.

Эти проверки относятся к конкретному пути чтения PostgreSQL. Они не превращают необязательную конечную точку RESP в универсальный сервер Redis и не инвалидируют значения, которые приложение уже скопировало в другое место.

Проверьте и откат, и коммит

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

  1. Записывающий сеанс может прочитать собственное изменение через путь исходной таблицы.
  2. Другой сеанс по-прежнему видит зафиксированное значение, пока запись открыта.
  3. После отката исходное значение сохраняется; после коммита новый оператор в режиме READ COMMITTED видит новую ревизию.

Третье условие относится к новому оператору. Оператор, начавшийся раньше, не обязан в середине выполнения принять более новый снимок. В контракте транзакций также описаны режимы, обходящие кэш. Для блокировки строк используйте обычный SQL.

Проверьте следующий кэш в приложении

DataLoader на уровне запроса всё ещё может содержать значение, загруженное до мутации. Инвалидация базы данных не может удалить этот объект JavaScript. Очищайте или заменяйте запись затронутого загрузчика после мутации согласно правилам авторизации и контракта результата приложения. Ограничивайте область загрузчиков одним запросом.

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