Transaktionsbewusste Cache-Invalidation in PostgreSQL

Das Löschen eines Cache-Eintrags reicht nicht aus, wenn ein früherer Lesevorgang ihn nach dem Löschen erneut füllen kann. Stellen Sie sich vor, ein Leser beginnt, eine alte Zeile zu laden, ein Schreiber bestätigt einen neuen Wert und invalidiert den Schlüssel, und anschließend veröffentlicht der frühere Loader sein Ergebnis. Ein Cache muss auch diese späte Veröffentlichung ablehnen.

Die Implementierung 2.0 setzt auf dem Datenbank-Schreibpfad Sperren für betroffene Schlüssel oder Relationen. Ein Fill trägt Generationsinformationen, damit es nach einer Invalidation abgelehnt werden kann. Positive Cache-Einträge tragen außerdem Tupel-Sichtbarkeitsinformationen. Ein ungeeigneter Eintrag fällt auf einen Quelltabellen-Lesevorgang zurück. Siehe die technische Referenz für den Vertrag.

Was Leser während einer Änderung sehen Unter READ COMMITTED starten wir mit Revision null. Eine Transaktion ändert sie auf eins. Der Schreiber sieht eins über einen Lesevorgang aus der Quelltabelle, während eine andere Sitzung weiterhin null sieht. Nach COMMIT sieht eine neue Anweisung in der anderen Sitzung eins. Nach ROLLBACK sieht sie weiterhin null. Bestätigte Zeilerevision = 0 BEGIN; UPDATE Transaktion offen Schreiber liest 1 Andere Sitzung liest 0 COMMIT ROLLBACK Nächste Abfragerevision = 1 Nächste Abfragerevision = 0
READ COMMITTED, beginnend mit Revision 0. „Nächster Lesevorgang“ bedeutet eine neue Anweisung in der anderen Sitzung nach COMMIT oder ROLLBACK. Der Schreiber liest seine eigene Änderung über die Quelltabelle.

Mit zwei Sitzungen testen

Starten Sie die lokale Demo. Öffnen Sie diesen Befehl in zwei Terminals:

docker compose -f examples/compose.yaml exec postgres \
  psql -X -v ON_ERROR_STOP=1 -U demo -d pglc_demo

Lesen Sie in Sitzung A Zeile 42 und notieren Sie ihre Revision, dann lesen Sie sie erneut, um sie aufzuwärmen:

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';

Aktualisieren Sie in Sitzung B die Zeile, lassen Sie die Transaktion aber offen:

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 sieht seine eigene Erhöhung. Dieser Lesevorgang umgeht den Cache. Wiederholen Sie As Abfrage, während B offen bleibt: A muss weiterhin die bestätigte Revision sehen, nicht Bs unbestätigten Wert. Führen Sie in B ROLLBACK aus; eine weitere Abfrage in A muss weiterhin die ursprüngliche Revision zurückgeben.

Führen Sie nun in B Folgendes aus:

BEGIN;
UPDATE public.items SET revision = revision + 1 WHERE id = 42;
COMMIT;

Eine in A nach diesem Commit gestartete Abfrage muss die erhöhte Revision zurückgeben. Dies ist die relevante Grenze: Eine ältere laufende Anweisung muss nicht auf einen nach ihrem Start aufgenommenen Snapshot wechseln. PostgreSQL dokumentiert dieses Verhalten unter Read Committed.

Der ausführbare Node.js-Test behauptet diese Beobachtungen mit getrennten Verbindungen.

Fälle, die den Cache absichtlich umgehen

REPEATABLE READ, SERIALIZABLE, Recovery, parallele Ausführung und Transaktionen, die zugeordnete Daten geschrieben haben, verwenden den Quelltabellenpfad. Eine übergroße Zeile kann erfolgreich zurückgegeben werden, ohne gecached zu werden. Eine Trefferrate nahe null bedeutet nicht unbedingt, dass die Installation fehlgeschlagen ist: Prüfen Sie Arbeitslast und Bypass-Zähler.

Wenn die Anwendung SELECT ... FOR UPDATE benötigt, verwenden Sie den normalen PostgreSQL-Vorgang; mget ersetzt keine Zeilensperre.

Ursache eines Fehltreffers prüfen

Verwenden Sie local_cache.stats() und local_cache.health() als Administrator. Vergleichen Sie Zähler-Snapshots vor und nach einem kontrollierten Test. Halten Sie SQL-mget-Zähler von RESP-Zählern getrennt. Folgen Sie nach absichtlichem DDL dem dokumentierten Verfahren für reconcile_table oder reconcile_all, statt anzunehmen, dass eine zuvor angehängte Zuordnung die geänderte Tabelle noch beschreibt.

Aktualisiert .