Инвалидация кэша PostgreSQL с учётом транзакций
Одного удаления записи кэша недостаточно, если более раннее чтение может заполнить её после удаления. Представьте: читатель начинает загружать старую строку, записывающий сеанс фиксирует новое значение и инвалидирует ключ, а затем тот ранний загрузчик публикует свой результат. Кэш должен отклонить и такую позднюю публикацию.
Реализация 2.0 ограждает затронутые ключи или отношения на пути записи в базу данных. Заполнение несёт сведения о поколении, поэтому после инвалидации его можно отклонить. Положительные записи кэша также содержат сведения о видимости кортежа. Недопустимая запись использует чтение исходной таблицы. См. технический справочник с описанием контракта.
Проверьте в двух сеансах
Запустите локальную демонстрацию. Выполните эту команду в двух терминалах:
docker compose -f examples/compose.yaml exec postgres \
psql -X -v ON_ERROR_STOP=1 -U demo -d pglc_demo
В сеансе A прочитайте строку 42 и запомните её ревизию, затем прочитайте её ещё раз для прогрева:
SELECT (local_cache.mget('public.items'::regclass, ARRAY[42]::bigint[]))[1]::jsonb ->> 'revision';
SELECT (local_cache.mget('public.items'::regclass, ARRAY[42]::bigint[]))[1]::jsonb ->> 'revision';
В сеансе B обновите строку, но оставьте транзакцию открытой:
BEGIN;
UPDATE public.items SET revision = revision + 1 WHERE id = 42;
SELECT (local_cache.mget('public.items'::regclass, ARRAY[42]::bigint[]))[1]::jsonb ->> 'revision';
B видит собственное увеличение. Это чтение обходит кэш. Пока B остаётся
открытым, повторите запрос A: A должен по-прежнему видеть зафиксированную
ревизию, а не незакоммиченное значение B. В B выполните ROLLBACK; следующий
запрос в A по-прежнему должен вернуть исходную ревизию.
Теперь выполните в B:
BEGIN;
UPDATE public.items SET revision = revision + 1 WHERE id = 42;
COMMIT;
Запрос A, начатый после этого коммита, должен вернуть увеличенную ревизию. Это важная граница: уже выполняющийся оператор не обязан переключаться на снимок, созданный после его запуска. PostgreSQL описывает это поведение в режиме Read Committed.
Исполняемый тест Node.js проверяет эти наблюдения через отдельные соединения.
Случаи, намеренно обходящие кэш
REPEATABLE READ, SERIALIZABLE, восстановление, параллельное выполнение и
транзакции, записавшие сопоставленные данные, используют путь исходной таблицы.
Слишком большая строка может быть успешно возвращена без кэширования. Частота
попаданий около нуля не обязательно означает неудачную установку: проверьте
нагрузку и счётчики обходов.
Если приложению нужен SELECT ... FOR UPDATE, используйте обычную операцию
PostgreSQL; mget не заменяет блокировку строк.
Изучите причину промаха
Используйте local_cache.stats() и local_cache.health() с правами
администратора. Сравнивайте снимки счётчиков до и после контролируемого теста.
Счётчики SQL mget держите отдельно от счётчиков RESP. После намеренного DDL
следуйте документированной процедуре reconcile_table или reconcile_all, а
не предполагайте, что ранее подключённое сопоставление по-прежнему описывает
изменённую таблицу.
Обновлено .