In der Softwareentwicklung gilt eine Transaktion als atomar: Entweder werden alle Änderungen vollständig übernommen oder gar nicht. Doch was passiert, wenn eine Operation mehrere unabhängige Datenbanken betrifft? Viele Entwickler nehmen an, dass zwei aufeinanderfolgende CommitAsync-Aufrufe eine zuverlässige Einheit bilden. Doch dieser Ansatz birgt ein fundamentales Risiko.
Die unsichtbare Bruchstelle zwischen zwei Commits
Stellen Sie sich vor, Sie führen eine Operation durch, die Daten in zwei physisch getrennten Datenbanken konsistent halten muss. Die naheliegende Lösung besteht darin, für jede Datenbank eine Transaktion zu öffnen, die Änderungen vorzunehmen und anschließend die Transaktionen nacheinander zu bestätigen. Ein typischer Code-Ausschnitt könnte so aussehen:
await using var txA = await dbA.Database.BeginTransactionAsync();
await using var txB = await dbB.Database.BeginTransactionAsync();
await DoWorkOnA(dbA);
await DoWorkOnB(dbB);
await txA.CommitAsync(); // Erster Commit — erfolgreich
await txB.CommitAsync(); // Zweiter Commit — was, wenn dieser fehlschlägt?Doch zwischen dem erfolgreichen Abschluss des ersten und dem Beginn des zweiten Commits entsteht eine kritische Phase. Schlägt die zweite Transaktion in diesem Moment fehl – etwa durch einen Verbindungsabbruch, einen Absturz des Prozesses oder eine Datenbankstörung –, ist die erste Transaktion bereits dauerhaft festgeschrieben. Ein Rollback ist dann nicht mehr möglich. Die Datenbanken geraten in einen inkonsistenten Zustand, und keine Exception-Handling-Logik kann diesen Umstand reparieren.
Warum seltene Fehler besonders gefährlich sind
Der gefährlichste Aspekt dieser vermeintlich sicheren Methode ist ihre Zuverlässigkeit im Alltag. Da die kritische Phase kurz ist, tritt die Inkonistenz selten auf. Entwickler gewöhnen sich an die scheinbare Stabilität und vertrauen auf den Mechanismus – bis zu dem Moment, in dem alles schiefgeht. Gerade bei Deployments, Failover-Tests oder Speichermangel, wenn die Systeme ohnehin unter Stress stehen, kann ein solcher Fehler katastrophale Folgen haben. Seltene, aber schwerwiegende Fehler sind aus Sicht der Softwareentwicklung oft gefährlicher als häufige, leicht erkennbare Probleme.
Lösungsansätze: Von falschen Annahmen zur echten Konsistenz
Die Illusion der Einfachheit entsteht durch die Annahme, dass zwei aufeinanderfolgende Commits eine atomare Einheit bilden. Doch dies ist eine grundlegende Fehleinschätzung. Die einzige Möglichkeit, echte Atomarität zu erreichen, besteht darin, die Architektur so umzugestalten, dass nur eine Transaktion für die kritische Operation verantwortlich ist.
1. Eindeutige Datenbank als primäre Quelle
Eine bewährte Strategie besteht darin, eine der beiden Datenbanken als primäre Quelle zu definieren. Alle Änderungen werden zunächst atomar in dieser Datenbank vorgenommen. Die zweite Datenbank dient anschließend als asynchrone Projektion, die über eine separate Logik aktualisiert wird. Der entscheidende Vorteil: Die primäre Datenbank bleibt immer konsistent. Die sekundäre Datenbank kann aufholen, ohne dass die Integrität der Gesamtoperation gefährdet ist.
2. Der Outbox-Pattern: Vertrauen durch Protokollierung
Der Outbox-Pattern bietet eine robuste Alternative zu direkten Schreiboperationen auf mehrere Datenbanken. Dabei wird innerhalb der primären Transaktion ein Eintrag in eine spezielle Outbox-Tabelle geschrieben. Ein separater Prozess liest diese Einträge und überträgt die Änderungen asynchron in die zweite Datenbank. Da die Outbox-Einträge atomar mit der primären Transaktion bestätigt werden, geht die Absicht der Änderung nie verloren – selbst wenn die Übertragung zur zweiten Datenbank fehlschlägt. Der Prozess kann die Änderungen wiederholt versuchen, bis sie erfolgreich sind.
3. Erkennung und Reparatur von Inkonsistenzen
Falls eine Umstrukturierung nicht möglich ist, sollte zumindest auf die Vorspiegelung falscher Atomarität verzichtet werden. Die Schreiboperationen in der zweiten Datenbank müssen idempotent und wiederholbar gestaltet werden. Zusätzlich sollte ein regelmäßiger Abgleich zwischen den Datenbanken durchgeführt werden, um Abweichungen zu erkennen und zu beheben. Dieser Ansatz ist kein vollständiger Ersatz für echte Atomarität, aber eine ehrliche Alternative zu einer scheinbar sicheren Lösung.
Die grundlegende Erkenntnis: Transaktionen enden an Systemgrenzen
Die eigentliche Herausforderung liegt nicht in der Implementierung einer Lösung, sondern in der Erkenntnis, dass zwei unabhängige Commits keine atomare Transaktion ergeben können. Sobald eine Operation mehrere Systeme oder Datenbanken berührt, verlässt man den Bereich klassischer Datenbanktransaktionen und betritt das Feld verteilter Systeme. Hier kommen Konzepte wie Outbox-Pattern, Sagas, Idempotenz und Reparaturmechanismen zum Einsatz – nicht ein zweiter CommitAsync-Aufruf.
Die vermeintliche Einfachheit von zwei aufeinanderfolgenden Commits ist eine Falle. Sie verleitet zu der Annahme, dass die Operation atomar ist, obwohl sie es nicht ist. Der erste Schritt zur Lösung besteht darin, diese Fehleinschätzung zu erkennen. Denn nur wer die Grenzen seiner Werkzeuge versteht, kann sie auch richtig einsetzen.
In der Praxis bedeutet dies: Wenn eine Operation mehrere Datenbanken betrifft, sollte man sich nicht auf die Illusion von Atomarität verlassen. Stattdessen gilt es, eine Architektur zu wählen, die entweder eine einzige Quelle der Wahrheit vorsieht oder Mechanismen wie den Outbox-Pattern nutzt, um die Konsistenz zu gewährleisten. Nur so lassen sich die kritischen Fehlerquellen vermeiden, die zwischen zwei Commit-Vorgängen lauern.
KI-Zusammenfassung
İki farklı veritabanında ardışık commit işlemleri atomik değildir. İşte bu yaygın yanılgının arkasındaki gerçekler, riskler ve güvenilir çözümler hakkında ayrıntılı bir rehber.