Wann wird eine CRDT-basierte Datenbank verwendet?

Roshan Kumar ist Senior Product Manager bei Redis Labs.

Das Biegen der Konsistenz und Verfügbarkeit, wie im CAP-Theorem beschrieben, war eine große Herausforderung für die Architekten geoverteilter Anwendungen. Netzwerkpartition ist unvermeidlich. Die hohe Latenz zwischen Rechenzentren führt immer zu einer kurzen Unterbrechung zwischen den Rechenzentren. Daher sind herkömmliche Architekturen für geoverteilte Anwendungen so konzipiert, dass sie entweder die Datenkonsistenz aufgeben oder die Verfügbarkeit beeinträchtigen.

Leider können Sie es sich nicht leisten, die Verfügbarkeit für interaktive Benutzeranwendungen zu opfern. In jüngster Zeit haben die Architekten einen Versuch zur Konsistenz unternommen und das letztendliche Konsistenzmodell übernommen. In diesem Modell hängen die Anwendungen vom Datenbankverwaltungssystem ab, um alle lokalen Kopien der Daten zusammenzuführen und letztendlich konsistent zu machen.

Mit dem eventuellen Konsistenzmodell sieht alles gut aus, bis Datenkonflikte auftreten. Einige mögliche Konsistenzmodelle versprechen die besten Anstrengungen zur Behebung der Konflikte, garantieren jedoch keine starke Konsistenz. Die gute Nachricht ist, dass die Modelle, die auf konfliktfreien replizierten Datentypen (CRDTs) basieren, eine starke Konsistenz bieten.

CRDTs erreichen eine starke Konsistenz durch einen vorgegebenen Satz von Regeln und Semantiken zur Konfliktlösung. Anwendungen, die auf CRDT-basierten Datenbanken basieren, müssen so konzipiert sein, dass sie die Konfliktlösungssemantik berücksichtigen. In diesem Artikel erfahren Sie, wie Sie geoverteilte Anwendungen mithilfe einer CRDT-basierten Datenbank entwerfen, entwickeln und testen. Wir werden auch vier Beispielanwendungsfälle untersuchen: Zähler, verteiltes Caching, gemeinsame Sitzungen und Datenerfassung in mehreren Regionen.

Mein Arbeitgeber, Redis Labs, hat kürzlich die CRDT-Unterstützung in Redis Enterprise angekündigt. Konfliktfreie replizierte Datentypen ergänzen das umfangreiche Portfolio an Datenstrukturen - Strings, Hashes, Listen, Sets, sortierte Sets, Bitfelder, Geo, Hyperloglog und Streams - in unser Datenbankprodukt. Die folgende Diskussion gilt jedoch nicht nur für Redis Enterprise, sondern für alle CRDT-basierten Datenbanken.

Datenbanken für geoverteilte Anwendungen

Bei geoverteilten Anwendungen werden häufig lokale Dienste für die Clients ausgeführt. Dies reduziert den Netzwerkverkehr und die durch den Roundtrip verursachte Latenz. In vielen Fällen entwerfen die Architekten die Dienste so, dass sie eine Verbindung zu einer lokalen Datenbank herstellen. Dann stellt sich die Frage, wie Sie konsistente Daten in allen Datenbanken verwalten. Eine Möglichkeit besteht darin, dies auf Anwendungsebene zu handhaben. Sie können einen regelmäßigen Jobprozess schreiben, der alle Datenbanken synchronisiert. Oder Sie können sich auf eine Datenbank verlassen, die die Daten zwischen den Datenbanken synchronisiert.

Für den Rest des Artikels gehen wir davon aus, dass Sie sich für die zweite Option entscheiden: Lassen Sie die Datenbank die Arbeit erledigen. Wie in Abbildung 1 unten dargestellt, führt Ihre geoverteilte Anwendung Dienste in mehreren Regionen aus, wobei jeder Dienst eine Verbindung zu einer lokalen Datenbank herstellt. Das zugrunde liegende Datenbankverwaltungssystem synchronisiert die Daten zwischen den in den Regionen bereitgestellten Datenbanken.

Redis Labs

Datenkonsistenzmodelle

Ein Konsistenzmodell ist ein Vertrag zwischen der verteilten Datenbank und der Anwendung, der definiert, wie sauber die Daten zwischen Schreib- und Lesevorgängen sind.

In einem Modell mit starker Konsistenz garantiert die Datenbank beispielsweise, dass die Anwendungen immer den letzten Schreibvorgang lesen. Bei sequentieller Konsistenz stellt die Datenbank sicher, dass die Reihenfolge der gelesenen Daten mit der Reihenfolge übereinstimmt, in der sie in die Datenbank geschrieben wurden. Im späteren Konsistenzmodell verspricht die verteilte Datenbank, die Daten zwischen den Datenbankreplikaten hinter den Kulissen zu synchronisieren und zu konsolidieren. Wenn Sie Ihre Daten in ein Datenbankreplikat schreiben und von einem anderen lesen, ist es daher möglich, dass Sie die neueste Kopie der Daten nicht lesen.

Starke Konsistenz

Das Zwei-Phasen-Commit ist eine übliche Technik, um eine starke Konsistenz zu erzielen. Hier gibt der Datenbankknoten für jede Schreiboperation (Hinzufügen, Aktualisieren, Löschen) an einem lokalen Datenbankknoten die Änderungen an alle Datenbankknoten weiter und wartet auf die Bestätigung aller Knoten. Der lokale Knoten sendet dann ein Commit an alle Knoten und wartet auf eine weitere Bestätigung. Die Anwendung kann die Daten erst nach dem zweiten Festschreiben lesen. Die verteilte Datenbank ist nicht für Schreibvorgänge verfügbar, wenn das Netzwerk die Verbindung zwischen den Datenbanken trennt.

Eventuelle Konsistenz

Der Hauptvorteil des möglichen Konsistenzmodells besteht darin, dass Ihnen die Datenbank zur Verfügung steht, um Schreibvorgänge auszuführen, selbst wenn die Netzwerkkonnektivität zwischen den verteilten Datenbankreplikaten ausfällt. Im Allgemeinen vermeidet dieses Modell die Umlaufzeit, die durch ein zweiphasiges Festschreiben entsteht, und unterstützt daher weitaus mehr Schreibvorgänge pro Sekunde als die anderen Modelle. Ein Problem, mit dem sich die eventuelle Konsistenz befassen muss, sind Konflikte - gleichzeitiges Schreiben auf dasselbe Element an zwei verschiedenen Stellen. Basierend darauf, wie sie Konflikte vermeiden oder lösen, werden die eventuell konsistenten Datenbanken weiter in die folgenden Kategorien eingeteilt:

  1. Letzter Schriftsteller gewinnt (LWW).  Bei dieser Strategie stützen sich die verteilten Datenbanken auf die Zeitstempelsynchronisation zwischen den Servern. Die Datenbanken tauschen den Zeitstempel jeder Schreiboperation zusammen mit den Daten selbst aus. Sollte es zu einem Konflikt kommen, gewinnt der Schreibvorgang mit dem neuesten Zeitstempel.

    Der Nachteil dieser Technik besteht darin, dass davon ausgegangen wird, dass alle Systemuhren synchronisiert sind. In der Praxis ist es schwierig und teuer, alle Systemuhren zu synchronisieren.

  2. Quorumbasierte eventuelle Konsistenz: Diese Technik ähnelt dem Zwei-Phasen-Commit. Die lokale Datenbank wartet jedoch nicht auf die Bestätigung aller Datenbanken. es wartet nur auf die Bestätigung von einem Großteil der Datenbanken. Die Anerkennung durch die Mehrheit ist beschlussfähig. Sollte es zu einem Konflikt kommen, gewinnt die Schreiboperation, die das Quorum festgelegt hat.

    Auf der anderen Seite erhöht diese Technik die Netzwerklatenz bei den Schreibvorgängen, wodurch die App weniger skalierbar ist. Außerdem ist die lokale Datenbank nicht für Schreibvorgänge verfügbar, wenn sie von den anderen Datenbankreplikaten in der Topologie isoliert wird.

  3. Zusammenführungsreplikation: Bei diesem traditionellen Ansatz, der in den relationalen Datenbanken üblich ist, führt ein zentraler Zusammenführungsagent alle Daten zusammen. Diese Methode bietet auch Flexibilität bei der Implementierung eigener Regeln zur Lösung von Konflikten.

    Die Zusammenführungsreplikation ist zu langsam, um Echtzeitanwendungen zu unterstützen. Es hat auch einen einzigen Fehlerpunkt. Da diese Methode keine voreingestellten Regeln für die Konfliktlösung unterstützt, führt dies häufig zu fehlerhaften Implementierungen für die Konfliktlösung.

  4. Konfliktfreier replizierter Datentyp (CRDT): In den nächsten Abschnitten erfahren Sie mehr über CRDTs. Kurz gesagt, CRDT-basierte Datenbanken unterstützen Datentypen und Vorgänge, die eine konfliktfreie Konsistenz bieten. CRDT-basierte Datenbanken sind auch dann verfügbar, wenn die verteilten Datenbankreplikate die Daten nicht austauschen können. Sie liefern immer lokale Latenz für die Lese- und Schreibvorgänge.

    Einschränkungen? Nicht alle Datenbankanwendungsfälle profitieren von CRDTs. Außerdem ist die Konfliktlösungssemantik für CRDT-basierte Datenbanken vordefiniert und kann nicht überschrieben werden.

Was sind CRDTs?

CRDTs sind spezielle Datentypen, die Daten aus allen Datenbankreplikaten zusammenführen. Die beliebten CRDTs sind G-Zähler (Nur-Wachstum-Zähler), PN-Zähler (Positiv-Negativ-Zähler), Register, G-Sätze (Nur-Wachstum-Sätze), 2P-Sätze (Zweiphasensätze), ODER-Sätze ( Beobachtete-Entfernen-Mengen) usw. Hinter den Kulissen stützen sie sich auf die folgenden mathematischen Eigenschaften, um die Daten zu konvergieren:

  1. Kommutative Eigenschaft: a ☆ b = b ☆ a
  2. Assoziative Eigenschaft: a ☆ (b ☆ c) = (a ☆ b) ☆ c
  3. Idempotenz:  a ☆ a = a

Ein G-Zähler ist ein perfektes Beispiel für eine operative CRDT, bei der die Operationen zusammengeführt werden. Hier ist a + b = b + a und a + (b + c) = (a + b) + c. Die Replikate tauschen nur die Aktualisierungen (Ergänzungen) miteinander aus. Das CRDT führt die Aktualisierungen zusammen, indem es sie addiert. Eine G-Menge wendet beispielsweise die Idempotenz ({a, b, c} U {c} = {a, b, c}) an, um alle Elemente zusammenzuführen. Durch Idempotenz wird die Duplizierung von Elementen vermieden, die einer Datenstruktur hinzugefügt werden, wenn diese sich auf verschiedenen Pfaden bewegen und konvergieren.

CRDT-Datentypen und ihre Konfliktlösungssemantik

Konfliktfreie Datenstrukturen: G-Zähler, PN-Zähler, G-Sets

Alle diese Datenstrukturen sind von Natur aus konfliktfrei. Die folgenden Tabellen zeigen, wie die Daten zwischen den Datenbankreplikaten synchronisiert werden.

Redis Labs Redis Labs

G-Zähler und PN-Zähler sind beliebt für Anwendungsfälle wie globale Abfragen, Stream-Zählungen, Aktivitätsverfolgung usw. G-Sets werden häufig zur Implementierung der Blockchain-Technologie verwendet. Bitcoins verwenden beispielsweise Blockchain-Einträge nur zum Anhängen.

Register: Saiten, Hashes

Register sind von Natur aus nicht konfliktfrei. Sie folgen in der Regel den Richtlinien der LWW oder der beschlussfähigen Konfliktlösung. Abbildung 4 zeigt ein Beispiel dafür, wie ein Register den Konflikt durch Befolgen der LWW-Richtlinie löst.

Redis Labs

Register werden hauptsächlich zum Speichern von Caching- und Sitzungsdaten, Benutzerprofilinformationen, Produktkatalog usw. verwendet.

2P-Sätze

Zweiphasensätze enthalten zwei Sätze von G-Sätzen - einen für hinzugefügte Elemente und einen für entfernte Elemente. Die Replikate tauschen die G-Set-Ergänzungen bei der Synchronisierung aus. Ein Konflikt entsteht, wenn in beiden Mengen dasselbe Element gefunden wird. In einigen CRDT-basierten Datenbanken wie Redis Enterprise wird dies durch die Richtlinie "Hinzufügen gewinnt über das Löschen" behandelt.

Redis Labs

Das 2P-Set ist eine gute Datenstruktur zum Speichern gemeinsam genutzter Sitzungsdaten wie Einkaufswagen, eines gemeinsam genutzten Dokuments oder einer Tabelle.

So erstellen Sie eine Anwendung für die Verwendung einer CRDT-basierten Datenbank

Das Verbinden Ihrer Anwendung mit einer CRDT-basierten Datenbank unterscheidet sich nicht vom Verbinden Ihrer Anwendung mit einer anderen Datenbank. Aufgrund der eventuellen Konsistenzrichtlinien muss Ihre Anwendung jedoch bestimmte Regeln befolgen, um eine konsistente Benutzererfahrung zu erzielen. Drei Schlüssel: 

  1. Machen Sie Ihre Anwendung zustandslos. Eine zustandslose Anwendung ist normalerweise API-gesteuert. Jeder Aufruf einer API führt dazu, dass die gesamte Nachricht von Grund auf neu erstellt wird. Dies stellt sicher, dass Sie zu jedem Zeitpunkt immer eine saubere Kopie der Daten abrufen. Die geringe lokale Latenz einer CRDT-basierten Datenbank macht die Rekonstruktion von Nachrichten schneller und einfacher. 

  2. Wählen Sie das richtige CRDT aus, das zu Ihrem Anwendungsfall passt. Der Zähler ist der einfachste der CRDTs. Es kann für Anwendungsfälle wie globales Abstimmen, Verfolgen aktiver Sitzungen, Messen usw. angewendet werden. Wenn Sie jedoch den Status verteilter Objekte zusammenführen möchten, müssen Sie auch andere Datenstrukturen berücksichtigen. Bei einer Anwendung, mit der Benutzer ein freigegebenes Dokument bearbeiten können, möchten Sie möglicherweise nicht nur die Änderungen, sondern auch die Reihenfolge beibehalten, in der sie ausgeführt wurden. In diesem Fall wäre das Speichern der Änderungen in einer CRDT-basierten Liste oder einer Warteschlangendatenstruktur eine bessere Lösung als das Speichern in einem Register. Es ist auch wichtig, dass Sie die von den CRDTs erzwungene Konfliktlösungssemantik verstehen und dass Ihre Lösung den Regeln entspricht.
  3. CRDT ist keine Einheitslösung. Während CRDT in der Tat ein großartiges Tool für viele Anwendungsfälle ist, ist es möglicherweise nicht für alle Anwendungsfälle (z. B. ACID-Transaktionen) das beste. CRDT-basierte Datenbanken passen im Allgemeinen gut zur Microservices-Architektur, bei der Sie für jeden Microservice eine eigene Datenbank haben.

Der wichtigste Aspekt hierbei ist, dass sich Ihre Anwendung auf die Logik konzentrieren und die Komplexität der Datenverwaltung und -synchronisierung an die zugrunde liegende Datenbank delegieren sollte.

Testen von Anwendungen mit einer verteilten Multi-Master-Datenbank

Um eine schnellere Markteinführung zu erreichen, empfehlen wir eine konsistente Entwicklung, Prüfung, Bereitstellung und Produktionskonfiguration. Dies bedeutet unter anderem, dass Ihr Entwicklungs- und Test-Setup über ein miniaturisiertes Modell Ihrer verteilten Datenbank verfügen muss. Überprüfen Sie, ob Ihre CRDT-basierte Datenbank als Docker-Container oder als virtuelle Appliance verfügbar ist. Stellen Sie Ihre Datenbankreplikate in verschiedenen Subnetzen bereit, damit Sie die Einrichtung verbundener und getrennter Cluster simulieren können.

Das Testen von Anwendungen mit einer verteilten Multi-Master-Datenbank kann komplex klingen. Die meiste Zeit werden Sie jedoch nur auf Datenkonsistenz und Anwendungsverfügbarkeit in zwei Situationen testen: Wenn die verteilten Datenbanken verbunden sind und wenn zwischen den Datenbanken eine Netzwerkpartition besteht.

Durch das Einrichten einer verteilten Datenbank mit drei Knoten in Ihrer Entwicklungsumgebung können Sie die meisten Testszenarien im Komponententest abdecken (und sogar automatisieren). Hier sind die grundlegenden Richtlinien zum Testen Ihrer Anwendungen: