- In Jepsen-Tests wurde ein Fall bestätigt, in dem Amazon RDS for PostgreSQL Multi-AZ-Cluster Snapshot Isolation nicht einhalten – die stärkste Isolationsstufe bezogen auf alle Knoten
- Die Hauptursache ist, dass die Reihenfolge, in der Transaktionen auf dem Primary sichtbar werden, durch In-Memory-Locks bestimmt wird, während Secondaries der WAL-Reihenfolge folgen; diese beiden Reihenfolgen können auseinanderlaufen
- Auch ohne Fehlerinjektion oder Failover traten unter Bedingungen mit
gp3-Storage unddb.m6id.large-Instanzen bei etwa 150 Write TPS / 1600 Read-only TPS alle paar Minuten G-nonadjacent Cycles auf - Die Anomalie entspricht einem Long Fork und trat in allen getesteten Versionen von PostgreSQL 13.15 bis 17.4 auf, die AWS unterstützt; Short Fork/Write Skew wurde nicht beobachtet
- Bei sicherheitskritischen Transaktionen kann die Ausführungsreihenfolge bei Nutzung von Read-only-Secondaries unterschiedlich erscheinen; daher sollte geprüft werden, nur den Writer Endpoint zu verwenden oder mindestens einen Write einzuschließen
Update zur Ursache von Long Fork
- Sergey Melnik von AWS sowie die HN-Kommentatoren matashii und Ants Aasma haben die Ursache von Long Fork in PostgreSQL-Clustern identifiziert
- Ein PostgreSQL-Primary bestimmt die Reihenfolge, in der Transaktionen sichtbar werden, über In-Memory-Locks
- Ein Secondary macht Transaktionen in der Reihenfolge sichtbar, in der sie im Write-Ahead Log (WAL) stehen
- Wenn Lock-Reihenfolge und WAL-Reihenfolge voneinander abweichen, können Primary und Secondary die scheinbare Reihenfolge von Transaktionen unterschiedlich sehen
- Dieses Verhalten wurde 2013 in einem Beitrag auf der PostgreSQL-Mailingliste behandelt, und Melnik verfasste im AWS-Blog einen Beitrag zur Transaction Visibility in PostgreSQL-Clustern und Read Replicas
- Jepsen empfiehlt AWS und PostgreSQL, dieses Problem zu dokumentieren und an einer Behebung zu arbeiten
Isolationsstufen und Architektur von RDS for PostgreSQL
- PostgreSQL ist eine universelle Open-Source-SQL-Datenbank und bietet per MVCC drei Transaktions-Isolationsstufen
Read UncommittedundRead Committedverhalten sich beide als Read CommittedRepeatable Readbietet tatsächlich nicht Repeatable Read, sondern Snapshot IsolationSerializablebietet Serializability
- Amazon RDS for PostgreSQL ist ein AWS-Service für verwaltete PostgreSQL-Cluster
- Er automatisiert Provisioning, Storage-Verwaltung, Replikation, Backups, Upgrades und mehr
- Multi-AZ Deployments verteilen Datenbankknoten über mehrere Availability Zones, um die Wahrscheinlichkeit korrelierter Ausfälle zu reduzieren
- RDS nutzt synchrone Replikation, sodass eine Antwort erst erfolgt, nachdem die Transaktionsdauerhaftigkeit sowohl auf dem Primary als auch auf mindestens einer Secondary-Instanz sichergestellt ist
- Nutzern werden zwei URLs bereitgestellt, die das PostgreSQL Wire Protocol sprechen
- Primary Endpoint: für Read-write-Transaktionen
- Reader Endpoint: für Read-only-Transaktionen
- Der Primary Endpoint unterstützt alle PostgreSQL-Isolationsstufen, Secondaries unterstützen jedoch kein Serializable
- Die stärkste über alle Knoten nutzbare Isolationsstufe ist Snapshot Isolation, die PostgreSQL
Repeatable Readnennt
Testdesign
- Jepsen passte seine Testbibliothek für PostgreSQL an Amazon RDS for PostgreSQL an und verwendete ein kleines Wrapper-Programm
- Für jede Testrunde wurde über die AWS-CreateDBCluster-API ein RDS-Cluster bereitgestellt
- Storage:
gp3 - Instanz:
db.m6id.large
- Storage:
- Ein EC2-Knoten für die Testausführung wurde gestartet und mit dem Main Endpoint sowie dem Read-only Endpoint des RDS-Clusters versorgt
- Es gab keine Fehlerinjektion und auch kein ausgelöstes Failover
- Die Haupt-Workload bestand aus Transaktionen, die Listen eindeutiger Ganzzahlen bearbeiten
- Jede Liste wird in einer einzelnen Row gespeichert und als
TEXT-Feld mit kommaseparierten Werten kodiert - Transaktionen lesen Listen per Primary Key oder hängen mit
CONCATeindeutige Ganzzahlen an Listen an
- Jede Liste wird in einer einzelnen Row gespeichert und als
- Mit dieser Workload kann der Elle Checker Datenflussabhängigkeiten zwischen Transaktionen ableiten und Graph-Cycles finden, um verschiedene Isolationsstufen zu prüfen
Beobachtete G-nonadjacent Cycles
- Unter normalen Bedingungen und bei mittlerer Parallelität zeigte Amazon RDS for PostgreSQL 17.4 alle paar Minuten G-nonadjacent Cycles
- Ein zweiminütiger Testlauf führte etwa 150 Write TPS und 1600 Read-only TPS aus und enthielt einen Cycle aus vier Transaktionen
- Der Beispiel-Cycle besteht aus den vier Transaktionen
T1,T2,T3,T4T1hängte an Row 89 den Wert9an und erzeugte die Liste[4 9], dieT2beobachteteT3hängte an Row 90 den Wert11an und erzeugte die Liste[11]T4hängte an Row 90 den Wert3an und las die Ergebnisliste[11, 3], wodurch es die Version vonT3überschriebT2beobachtete in Row 89 den Append vonT1, sah aber in Row 90 den Append vonT3nicht- Umgekehrt beobachtete
T4in Row 90 den Append vonT3, verpasste aber in Row 89 den Append vonT1
- Da dieser Cycle nicht benachbarte Read-write Dependencies enthält, ist er ein G-nonadjacent Cycle und damit eine Verletzung von Snapshot Isolation
- Unter
Repeatable Readim Standard-PostgreSQL sollte dieses Verhalten nicht auftreten, und Jepsen konnte es in Standard-PostgreSQL nicht beobachten
Warum dies Snapshot Isolation widerspricht
- Bei Snapshot Isolation muss jede Transaktion so erscheinen, als würde sie auf einem Datenbank-Snapshot zum Start-Timestamp
sarbeiten - Die Effekte einer Transaktion werden für andere Transaktionen zu einem späteren Commit-Timestamp
csichtbar - Schreibt man die Beobachtungen aus dem Beispiel-Cycle als Timestamp-Beziehungen, ergeben sich Widersprüche
- Da
T2den Append vonT1gelesen hat, muss der Start vonT2nach dem Commit vonT1liegen:c1 < s2 - Da
T2den Append vonT3nicht beobachtet hat, gilt:s2 < c3 - Da
T4T3überschrieben und beobachtet hat, gilt:c3 < s4 - Da
T4den Append vonT1nicht beobachtet hat, gilt:s4 < c1
- Da
- Diese Beziehungen können nicht alle gleichzeitig gelten und widersprechen daher dem Timestamp-Modell von Snapshot Isolation
Long Fork und Ergebnisse nach Version
- Der Cycle ist auch ein Beispiel für Long Fork
- Die erste und zweite Transaktion bilden einen logischen State-Fork
- Die dritte und vierte Transaktion bilden den zweiten Fork
- Die beiden Forks aktualisieren unterschiedliche Rows, beobachten aber die Effekte des jeweils anderen nicht
- Short Fork, also Write Skew, wurde nicht beobachtet
- Dieses Ergebnis deutet darauf hin, dass Amazon RDS for PostgreSQL möglicherweise Parallel Snapshot Isolation bietet, eine etwas schwächere Form als Snapshot Isolation
- G-nonadjacent-Anomalien traten in unterschiedlichen Formen auf, darunter Fälle, die nur durch Write-read Edges verbunden waren, sowie Fälle mit mehr als vier Transaktionen
- In allen getesteten Versionen, von PostgreSQL 13.15 als ältester von AWS unterstützter Version bis zur neuesten Version 17.4, trat dieselbe Art von Anomalie auf
Was Nutzer prüfen sollten
- Da Long Fork und andere G-nonadjacent Cycles existieren, garantieren Amazon RDS for PostgreSQL Multi-AZ-Cluster keine Snapshot Isolation
- In dieser Hinsicht bieten RDS-for-PostgreSQL-Multi-AZ-Cluster schwächere Sicherheitssemantiken als Single-Node-PostgreSQL, das in früheren Jepsen-Tests Strong Snapshot Isolation zu bieten schien
- Nutzer können prüfen, ob ihre Transaktionsstruktur anfällig für Long Fork ist, oder experimentell verifizieren, ob beabsichtigte Invarianten eingehalten werden
- Read-Transaktionen können hinsichtlich der Transaktionsausführungsreihenfolge andere Ergebnisse sehen als andere Transaktionen
- Da die Anomalien offenbar mit Queries gegen Read-only-Secondaries zusammenhängen, könnte Snapshot Isolation möglicherweise mit folgenden Ansätzen wiederhergestellt werden
-
Nur den Writer Endpoint verwenden
- In alle sicherheitskritischen Transaktionen mindestens einen Write aufnehmen
- Jepsens Validierung ist ein experimenteller Ansatz; sie kann die Existenz von Bugs nachweisen, aber nicht deren Abwesenheit
- Dieser Bericht ist nicht das Ergebnis einer detaillierten Untersuchung des Verhaltens von RDS for PostgreSQL, sondern einer vorläufigen Erkundung
-
1 Kommentare
Meinungen auf Hacker News
Ich wünschte, Texte in der Softwarewelt wären häufiger so: „Amazon RDS for PostgreSQL ist ein Service von Amazon Web Services (AWS), der verwaltete Instanzen von PostgreSQL-Datenbanken bereitstellt. Wir zeigen, dass Amazon RDS for PostgreSQL multi-AZ clusters Snapshot Isolation verletzen, das stärkste Konsistenzmodell, das über alle Endpunkte hinweg unterstützt wird …“
Direkt, auf den Punkt und ohne Ausschmückung – ähnlich wie die Kommunikation von Forschungsergebnissen in anderen STEM-Bereichen. Früher mochte ich geistreiche Blogposts, die Dinge mit Memes erklärten, aber inzwischen sehne ich mich nach plain und einfachen Texten.
Wenn ich sehr tiefgehende technische Texte schrieb, gab es kaum Likes und Kommentare; ein Staff Engineer meinte sogar, ich solle die Zielgruppe enger fassen. Umgekehrt schrieb ich beim Testen des frühen Kubecost einen Beitrag darüber, dass die Empfehlungen nur geringe Kosteneinsparungen bringen und Performance-Probleme bei Containern verursachen könnten; obwohl der Artikel mit CPU throttling und cgroups ziemlich technisch war, fanden ihn die Leute großartig, sobald ich Memes einbaute.
Später schrieb ich einen noch trockeneren Beitrag darüber, eine kleine externe Python-Bibliothek in C zu erstellen, per ctypes darauf zuzugreifen und Stack-/Heap-Allokation zu vergleichen; mit Memes erzielte er ein ähnliches Ergebnis. Mir gefällt diese Entwicklung nicht, aber ich weiß auch kaum, wie man sonst ein breiteres Publikum erreicht. Jensen zielte nicht auf diese Leserschaft ab, und das strenge, unverfälschte Schreiben verdient Applaus.
Es steht weder im Titel noch ist es im Artikel ganz eindeutig, aber dieses Problem ist auf multi-AZ clusters beschränkt, eine relativ neue Funktion von RDS. Das ist etwas anderes als die multi-AZ instances, mit denen viele vertraut sind.
multi-AZ instances sind die ältere Funktion, bei der die primäre DB synchron auf eine sekundäre DB in einer anderen Availability Zone repliziert wird und RDS bei einem Ausfall der primären auf die sekundäre umschaltet.
multi-AZ clusters haben zwei sekundäre Knoten, und Transaktionen werden synchron auf mindestens einen davon repliziert. Wenn ein sekundärer Knoten ausfällt oder langsamer wird, ist das robuster als multi-AZ instances; außerdem ist Read-only-Zugriff auf die sekundären Knoten möglich.
Allerdings steckt in multi-AZ clusters intern vermutlich mehr zusätzliche Magie, die nicht zu den Standardfunktionen von PostgreSQL gehört, und wahrscheinlich sind sie deshalb im Jepsen-Test gescheitert.
Allerdings gibt es in PostgreSQL noch einen Defekt, der ein ähnliches Problem wie dieses Muster möglich macht. Eine nicht replizierte Transaktion, bei der der Client während des Commits verschwindet, wird sofort sichtbar. Wenn im Beispiel T1 auf einem abgetrennten Leader stattfindet und die Verbindung während des Commits abbricht, T2 ebenfalls auf dem abgetrennten Knoten stattfindet und T3/T4 später auf dem neuen Leader stattfinden, kann man dasselbe Ergebnis sehen. Das passt allerdings nicht gut zu der Aussage, dass in diesem Test kein Fault Injection vorgenommen wurde.
Korrektur: Ich hatte nicht gesehen, dass der Artikel dieses Muster durch eine abweichende Commit-Reihenfolge zwischen Replikat und primärem Knoten erklärt. Ein bisschen peinlich, weil ich schon einmal vorgestellt habe, wie man dieses Problem behebt.
Gute Untersuchung. Viele Softwareentwickler kennen heutzutage nicht einmal Transaktionen richtig, geschweige denn die verschiedenen Transaktionsmodelle. Ich habe sogar unter CRUD-Entwicklern, die als „Senior Developer“ bezeichnet werden, Leute gesehen, die von Datenbanktransaktionen überhaupt keine Ahnung hatten.
Wenn es in der Praxis ein gewisses Traffic-Volumen gibt und die Software nicht triviale Probleme löst, sind Transaktionen und Transaktionsmodelle extrem wichtig für Performance und fehlerfreien Code.
In einem großen Projekt haben wir zum Beispiel nach umfangreicher Analyse von SQL Servers standardmäßigem Read Committed auf Read Committed Snapshot Isolation umgestellt, und die Lock Contention verschwand weitgehend, was die Nutzer sehr zufrieden machte. Die Software Engineers in diesem Projekt nutzten zwar viele Transaktionen, wussten aber nichts über Transaktionsmodelle oder Locks, bis man ihnen die Grundlagen beibrachte.
Da ich hauptsächlich im Retail-Bereich arbeite, sehe ich oft Systeme voller Fehler, die Race Conditions ähneln; umso bedauerlicher ist es, weil solche Isolationsstufen hier sehr helfen könnten.
Allerdings habe ich solche Fälle vor allem bei Startup-Engineers gesehen; die typischen Oracle-/MSSQL-Entwickler in Großunternehmen schätze ich recht hoch ein, weil bei ihnen zumindest die Grundlagen stimmen.
Ein paar Mal in meiner Karriere habe ich gesehen, dass dieser Ansatz wirklich schlechte Ergebnisse liefert.
Bei dieser Umstellung muss man allerdings beachten, dass jeder Code kaputtgeht, der sich auf blockierende Reads verlässt. Code wie etwa
select with existsmuss mit expliziten Locks oder auf andere Weise neu geschrieben werden.In einer früheren Firma sahen wir selten Fehler, die auf Inkonsistenzen hindeuteten, etwa Duplicate-Key-Fehler und Foreign-Key-Constraint-Fehler während der Wiederherstellung, nachdem wir den
pg_dump-Befehl im Backup-Skript geändert hatten, um parallele Worker zu verwenden (Flag-j).Damals wollten wir das bei AWS und auf der PostgreSQL-Mailingliste melden, konnten es aber nicht leicht reproduzieren, kamen deshalb nicht weiter und gaben schließlich auf, indem wir zu Single-Thread-Dumps zurückkehrten. Ich frage mich, ob das Phänomen, das wir damals gesehen haben, mit diesem Problem zusammenhängt.
Wenn man diesen Artikel liest, scheint die tatsächliche Auswirkung zu sein, dass bei einem schnellen Read direkt nach einem Write auf dieselbe Zeile veraltete Daten zurückgegeben werden können. Die Write-Transaktion wird als abgeschlossen markiert, aber bevor die verteilte Schicht der Multi-AZ-RDS-Instanz vollständig aktualisiert ist, kann ein sofortiger Read derselben Zeile dazu führen, dass die Zeile noch nicht existiert oder Spalten noch nicht vollständig aktualisiert sind und daher der alte Wert erscheint.
Aufgrund der Snapshot-Methode von PostgreSQL scheint das nicht zu bedeuten, dass nur einige Bytes eines Multi-Byte-Spaltentyps aktualisiert werden und man dadurch unsinnige Werte liest.
Am Ende wirkt es wie eine Race Condition, die sich mit der Zeit konvergiert. Oder hat jemand das so gelesen, dass spätere Transaktionen im „long fork“ auch unter normalen Bedingungen möglicherweise nie abgeschlossen werden?
Die Formulierung „Diese Arbeit wurde ohne Vergütung unabhängig von Jepsen durchgeführt“ ist etwas, das RDBMS-Stakeholder selbst an guten Tagen nicht gern sehen. Intern dürften ein paar E-Mails mit Bedenken herumgegangen sein. Wie immer: Respekt an aphyr.
Es ist nicht ganz klar, ob das in multi-instance-Upstream-PostgreSQL-Clustern kein Problem ist. Ich frage mich, ob es richtig ist, davon auszugehen, dass AWS in der Cluster-Konfiguration irgendetwas macht oder einen Patch hinzugefügt hat, der dieses Verhalten auslöst.
PostgreSQL-Replikation gibt es im Allgemeinen in vielen Varianten, und die Ergebnisse unterscheiden sich. Ein Beispiel ist Bin Wangs Patroni-Bericht: https://www.binwang.me/2024-12-02-PostgreSQL-High-Availabili...
Was hier ebenfalls festgestellt wurde: PostgreSQL bietet derzeit kein konsistentes Snapshot-Verhalten zwischen Primärknoten und Replikat. Vermutlich lief die Read-only-Transaktion T2 auf einem Sekundärknoten, während die schreibenden Transaktionen T1/T3/T4 auf dem Primärknoten liefen.
Zum Hintergrund: Der Snapshot eines sekundären PostgreSQL-Knotens stützt sich bei der Entscheidung, welche Transaktionen sichtbar sind, auf die Persistenzreihenfolge der Transaktionen, also auf die Position der Commit-Records im WAL. Auf dem Primärknoten wird die Sichtbarkeitsreihenfolge dagegen dadurch bestimmt, wann das Backend, das die betreffende Transaktion bestätigt hat, erstmals die Benachrichtigung erhält, dass die Transaktion vollständig committet wurde, und wann es danach die Commit-Markierung setzt.
Innerhalb des Primär- bzw. Sekundärknotens ist die Commit-Reihenfolge zwischen den verbundenen Backends jeweils konsistent, aber zwischen Primär- und Sekundärknoten kann sich die Commit-Reihenfolge etwas unterscheiden. Es wird daran gearbeitet, das zu verbessern, aber das ist noch in einem sehr laufenden Stadium.
AWS scheint PostgreSQL so zu patchen, dass auf zwei Instanzen repliziert wird und es genügt, wenn eine der beiden die Änderung bestätigt. Wann diese Bestätigung erfolgt, ist nicht öffentlich bekannt.
Persönlich halte ich bei PostgreSQL Replikation auf Dateisystemebene wie drbd für besser. Die ältere Variante der AWS-Multi-AZ-Instanzen dürfte wahrscheinlich so funktioniert haben. Allerdings sinkt dabei der Durchsatz, und man kann nicht von der Sekundärinstanz lesen.
Insbesondere diese Stelle: https://youtu.be/fLqJXTOhUg4?t=434
Der eingereichte Titel fragt nach dem Kernpunkt: RDS for PostgreSQL 17.4 implementiert Snapshot Isolation nicht korrekt.
Die Diskussionen können ziemlich heftig werden: ob der Titel dem System gegenüber zu hart oder zu freundlich ist, ob er das Bedeutendste unter den rund ein Dutzend gefundenen Problemen enthält, ob er nach Jepsens Anspruch, ein ehrlicher Vermittler bei Ergebnissen zur Datenbanksicherheit zu sein, fair ist, und wie er in zehn Jahren zu verstehen ist, wenn Leute ihn weiterhin verlinken, er aber für neuere Versionen nicht mehr gilt.
Nach einigen frustrierenden Versuchen umgehe ich dieses Problem inzwischen mit der Regel, alle Berichtstitel nach dem Muster „Jepsen: “ zu benennen. Wenn HN erklärendere oder farbigere Linktexte möchte, kann es sie natürlich selbst auswählen.
Trotzdem stammt das von Kyle Kingsbury, dem Chuck Norris der Transaktionsgarantien, also sollte AWS antworten oder das klarstellen. Auch dann, wenn es offenbar nur für eine der beiden Optionen von RDS for PostgreSQL gilt, nämlich Multi-AZ-Cluster. Multi-AZ-Deployments können eine oder zwei Standby-DB-Instanzen haben; hier geht es um die Konfiguration mit zwei Standby-DB-Instanzen.
In der AWS-Dokumentation gibt es kein solches Versprechen. Auch das 5494 Seiten starke RDS-Handbuch erwähnt isolation oder serializable praktisch nur in den Parameterdokumentationen der jeweiligen Engines.
Auch zur globalen Lesekonsistenz von Multi-AZ-Clustern steht dort nichts. Es heißt zwar, dass bei semi-synchroner Replikation der Writer auf die Bestätigung der Log-Records durch einen Standby wartet, aber zwei Reader können sich auf unterschiedlichen Snapshots befinden.
[1] - "New Amazon RDS for MySQL & PostgreSQL Multi-AZ Deployment Option: Improved Write Performance & Faster Failover" - https://aws.amazon.com/blogs/aws/amazon-rds-multi-az-db-clus...
[2] - "Amazon RDS Multi-AZ with two readable standbys: Under the hood" - https://aws.amazon.com/blogs/database/amazon-rds-multi-az-wi...
Wenn Entwickler Snapshot Isolation voraussetzen, Amazon RDS for PostgreSQL tatsächlich aber nur parallele Snapshot Isolation bietet, frage ich mich, welche Sicherheits- oder Bugs auf Anwendungsebene daraus entstehen können – insbesondere in Multi-AZ-Konfigurationen, die einen Read-Replica-Endpunkt verwenden.
git pushvorstellen. Man startet eine Transaktion, liest den aktuellen Zustand, prüft, ob er dem erwarteten Zustand entspricht, schreibt den neuen Zustand und committet ihn zusammen mit dem Hash des neuen Zustands. In einem unglücklichen Fall kann dabei ein Commit-Hash entstehen, der zu keinem gültigen Zustand passt.Schon die Tatsache, dass sich so etwas schwer nachvollziehen lässt, macht es schwierig, das Problem zu vermeiden. Die einfachste Lösung dürfte daher sein: Wenn ein Schreibvorgang von einem Lesevorgang abhängt, könnte man der Snapshot Isolation vielleicht nahekommen, indem man „nur den Writer-Endpunkt verwendet“.
Überraschend ist allerdings, dass der Ansatz „nur den Writer-Endpunkt verwenden“ offenbar nicht getestet wurde, insbesondere bei Verfügbarkeitsverlusten.
User1 schreibt einen Kommentar, User2 schreibt danach ebenfalls einen Kommentar, und anschließend prüft User1 in einer separaten Transaktion, dass es nur einen Kommentar gibt, und erhält das Badge. User2 kann in einer separaten Transaktion dieselbe Prüfung durchführen, nur seinen eigenen Kommentar sehen und ebenfalls das Badge erhalten.
Bei Snapshot Isolation wäre das unmöglich. Mindestens eine der separaten Transaktionen müsste zwei Kommentare sehen.
Auch das ursprüngliche Paper zu parallelen Snapshots ist lesenswert: https://scispace.com/pdf/transactional-storage-for-geo-repli...
Als ich den Satz „Dieses Verhalten trat in allen getesteten Versionen auf, von 13.15 bis 17.4“ las, war ich besorgt, ob das Upgrade auf die neue Major-Version eine falsche Entscheidung gewesen sei. Das scheint aber nicht der Fall zu sein. Es handelt sich eher um einen Feature Request oder einen alten Bug als um eine Regression.