Transaction-aware cache invalidation in PostgreSQL
This guide explains how attached-table triggers prevent a committed PostgreSQL write from being followed by a stale RESP cache hit.
A trigger records dirty keys or a dirty relation inside the writer transaction. At commit, the extension publishes invalidation fences and advances generations. A fill that started before the fence cannot publish stale data. Rollback before fence publication discards the transaction’s dirty state, so prior entries remain valid. If the transaction aborts after publication, invalidation is not undone and affected entries remain invalid.
See the technical consistency reference for the full read-path contract.
Check invalidation across SQL and RESP
Start the local demo, then read the same key from RESP and PostgreSQL:
MGET CRUD:pglc_demo.public.items:{"id":42}
In a separate SQL session, update and commit:
BEGIN;
UPDATE public.items SET revision = revision + 1 WHERE id = 42;
COMMIT;
The next RESP command returns the committed revision. If the writer rolls back instead, RESP continues to return the last committed revision.
RESP uses the configured PostgreSQL role in an independent short transaction. It does not share the application’s role, transaction, or snapshot. Use SQL in the application transaction for read-your-writes and SELECT ... FOR UPDATE.
Cases that bypass the cache
With pg_local_cache.enabled off, RESP MGET skips cache lookup and fill and reads the source table. An active dirty fence for a key, relation, or globally also blocks cache hits and fills, so reads use the source table. RESP workers start after recovery finishes; recovery is not a separate bypass condition. A row too large for one cache entry may still be returned from PostgreSQL if its JSON fits the RESP value limit, without being stored.
Inspect the cause of a miss
Compare local_cache.stats() and local_cache.health() before and after a controlled workload. Check bypass, miss, invalidation, and mapping-reload counters. See the technical metrics list.
Updated .