Zwei Anwendungsserver in zwei Regionen, ein Client, der über ein VPN schreibt, und eine Frage, die niemand aufgeschrieben hat: Welche Komponente entscheidet, dass der Schreibvorgang stattgefunden hat? Routing bringt die Anfrage zu einem Prozess. Eine Transaktionsgrenze entscheidet, wann die Änderung dauerhaft und sichtbar wird. Das sind verschiedene Aufgaben, die von verschiedenen Systemen erledigt werden, und die Lücke dazwischen ist der Ort, an dem die meisten Fehler beim regionsübergreifenden Schreiben leben.

Beginnen Sie bei der Geschäftsregel, nicht bei der Landkarte

Beginnen Sie mit einem Satz über die Geschäftsregel statt über die Geografie: Welche Lese- und Schreibvorgänge müssen gemeinsam gelingen, und welche gleichzeitigen Änderungen müssen dabei unmöglich sein. Bei einer Bestandsreservierung heißt das, dass Bestandsprüfung und Reservierung entweder beide wirksam werden oder keine, und keine zweite Bestellung darf dazwischen dieselbe Einheit verbrauchen.

RequirementWhat satisfies itWhat does not
All-or-nothing applicationA transaction inside one owning systemA VPN path between regions
No double spendIsolation strong enough to exclude the conflictAtomicity alone
Survive a region lossReplication the database itself documentsTwo independent databases behind one client

Genau diese mittlere Zeile überspringen Teams. Atomarität ohne ausreichende Isolation erlaubt es zwei Transaktionen, jeweils korrekt zu committen und trotzdem einen Zustand zu erzeugen, den das Geschäft für unmöglich hält, und keine Menge an Wiederholungslogik repariert das hinterher.

Eine maßgebliche Grenze schlägt zwei regionale Server

Regionale Anwendungsserver können bleiben, wo sie sind, solange sie die Operation an einen Dienst übergeben, der sie innerhalb einer einzigen unterstützten Transaktionsgrenze ausführt. Die Anwendungsschicht wird zum Router der Absicht; die Datenschicht besitzt die Wahrheit.

Halten Sie diese Grenze kurz und nah an den Daten, die sie berührt. Eine Transaktion, die offen gehalten wird, während ein Mensch eine Eingabeaufforderung beantwortet oder während ein Aufruf zwei Regionen überquert, verbringt ihre Zeit damit, Sperren zu halten, und erhöht die Chance, dass etwas anderes dahinter in einen Timeout läuft. Dieser Fehler sieht wie ein Datenbankproblem aus und ist in Wahrheit ein Designproblem, das sich als eines verkleidet hat. Die Entscheidung in die Datenschicht zu verlagern, beseitigt ihn meist vollständig.

BEGIN;
UPDATE inventory SET reserved = reserved + 1
 WHERE sku = 'A-100' AND available >= 1;
INSERT INTO reservation (sku, order_id) VALUES ('A-100', 'ord-9f');
COMMIT;

Lesen Sie diese Skizze als Grenze, nicht als Leistungsempfehlung. Der Punkt ist, dass Prüfung und Einfügen einen Commit teilen, sodass eine gleichzeitige Bestellung den Bestand entweder verbraucht sieht oder ihn gar nicht sieht.

Was eine replizierte Datenbank liefert und was nicht

Eine replizierte Datenbank kann eine breitere Grenze liefern, wenn ihr eigenes Transaktionsmodell dies unterstützt. Spanner dokumentiert atomare Lese-Schreib-Transaktionen innerhalb einer einzigen Datenbank über Tabellen hinweg, mit definierter Isolation und definiertem Wiederholungsverhalten. Das ist eine Garantie, die man liest, testet und auf die man sich bewusst verlässt.

Es ist auch eine Garantie, die vom Produkt kommt, nicht von der Route. Ein VPN zwischen Regionen verändert, wie Pakete reisen und wie sich Fehler zeigen. Es erweitert eine Transaktionsgrenze nicht um eine einzige Zeile, so gesund der Tunnel auch aussehen mag. Die beiden zu verwechseln erzeugt Architekturen, in denen jede Komponente ihre eigene Prüfung besteht und die Daten trotzdem nicht übereinstimmen.

Zwei Datenbanken werden nicht zu einem System

Zwei unabhängig verwaltete Datenbanken erhalten keinen gemeinsamen Transaktionsmanager, nur weil Clients beide erreichen können. Braucht das Geschäft wirklich eine atomare Zusage über das Paar hinweg, müssen Koordinationsmechanismus, Teilnehmer und Wiederherstellungsprozess ausgewählt und qualifiziert werden, statt angenommen zu werden.

Denken Sie an den Moment, in dem ein Teilnehmer oder eine Verbindung verschwindet, nachdem Arbeit vorbereitet wurde. Ein unterstützter verteilter Commit kann einen ungewissen Zustand hinterlassen, der ein Wiederherstellungsverfahren und eine menschliche Entscheidung braucht, und er kommt ohne das Versprechen, dass jede Region durch jede Partition hindurch beschreibbar bleibt.

Ist stattdessen asynchrone Koordination akzeptabel, sagen Sie es ausdrücklich und schreiben Sie die Zwischenzustände des Geschäfts sowie die Abgleichsregeln auf. Eine Kette lokal committeter Schritte mit Kompensation ist ein legitimes Design; sie ist nur keine atomare Transaktion unter anderem Namen.

Entwerfen Sie den Wiederholversuch vor dem ersten Ausfall

Ein Client kann die Antwort verlieren, nachdem die Operation committet wurde, also darf der Aufrufer einen Timeout nicht als Fehler behandeln. Bewahren Sie eine dauerhafte Anfrageidentität und nutzen Sie das Dienstergebnis oder einen Idempotenzmechanismus, bevor Sie eine Änderung wiederholen, sonst wird der Wiederholversuch zu einem zweiten Schreibvorgang.

Automatische Transaktions-Callbacks können nach einem wiederholbaren Abbruch auch mehr als einmal ausgeführt werden, was sie zu einem schlechten Ort für unumkehrbare externe Effekte macht. Halten Sie diese aus dem Callback heraus, es sei denn, der Workflow definiert ein ausdrückliches sicheres Protokoll dafür. Dieselbe Vorsicht gilt für alles, was Geld kostet oder eine Nachricht sendet. Eine doppelte Belastung ist ein viel schwierigeres Gespräch als eine doppelte Logzeile.

Die Abnahme sollte vier Szenarien nachspielen: zwei Clients, die um dieselbe Zeile rennen, einen Abbruch, den der Dienst als wiederholbar bezeichnet, einen Commit, dessen Antwort nie ankommt, und den Verlust einer Region oder der Verbindung zwischen ihnen. Halten Sie das committete Geschäftsergebnis und die ungelösten Zustände neben dem Routingdesign fest, damit niemand das Netzwerk für die Komponente hält, die die Garantie liefert.

Ähnliche Artikel