Der falsche Weg, Cassandra DB zu skalieren, wenn sekundäre Indizes vorhanden sind
Cassandra ist aus vielen Gründen meine bevorzugte (nicht verwaltete) Datenbank: Sie hat keinen Single Point of Failure (SPoF), unterstützt mehrere Regionen, eignet sich gut für Lese- und Schreibvorgänge, ist flexibel in Bezug auf Lese- und Schreibkonsistenzstufen, skaliert linear und ist für den täglichen Betrieb nicht zu komplex zu verwalten.
Wie jede Datenbank sollten Sie Cassandra basierend auf Ihren Datenzugriffsmustern verwenden. Wenn Sie also eine flexible Datenbank für Ad-hoc-Abfragen oder anpassungsfähig genug für ständige Änderungen des Datenbankmodells benötigen, sollten Sie andere Optionen in Betracht ziehen.
Cassandra ist eine spaltenorientierte Datenbank und sehr leistungsfähig, wenn Sie Ihre Datenabfragen bereits definiert haben. Datastax, das Unternehmen, das Cassandra unterstützt, empfiehlt, dass Sie zunächst Ihre Abfragen und dann Ihr Datenmodell in Cassandra entwerfen. Trotz Ihrer Spaltenstruktur unterstützt Cassandra viele Datenstrukturen als Spaltentyp, z. B. Karten.
Cassandra ist eine Primärschlüsseldatenbank, was bedeutet, dass Ihre Daten persistent sind und in einem Cluster organisiert sind, der auf dem Hashwert (dem Partitionsschlüssel) des Primärschlüssels basiert. Bei Tabellen mit mehr als einer PK berücksichtigt Cassandra nur den ersten Teil der PK als Partitionsschlüssel. Weitere Informationen zu Composite Keys finden Sie hier.
Um es klarer zu machen, kehren wir zu einer der wichtigsten Eigenschaften einer Cassandra-Datenbank zurück: es ist Architektur und die Tatsache, dass es keinen SPoF hat.
Ein Cassandra-Cluster besteht aus Knoten (3 oder mehr), und diese Knoten bilden zusammen einen Knotenring:
Jeder Knoten in einem Cassandra-Cluster arbeitet “unabhängig”, aber verschiedene Knoten können dieselben Daten entsprechend der für den Cluster konfigurierten RF-Konfiguration (Replication Factor) speichern.
Um zu wissen, wo (welcher Knoten) Ihre Daten gespeichert sind, verwendet Cassandra den Hashwert (Token), der durch eine konsistente Hash-Funktion unter Verwendung der PK-Spalte einer bestimmten Tabelle berechnet wird.
Wenn Sie eine Abfrage ausführen, sucht der Koordinatorknoten (normalerweise der nächstgelegene Ihrer Anwendungsinstanzen), welche Knoten in einem Ring Ihre Daten enthalten, auf diese Weise, wenn ein Knoten aus irgendeinem Grund ausgefallen ist, ein anderer Knoten könnte Ihre Daten bereitstellen (RF ≥2). Das ist die Magie eines Masterless-Ansatzes, bei dem jeder Knoten in einem Ring in Bezug auf Lese- und Schreibvorgänge gleich ist.
Dieses Konzept über PK und den Replikationsfaktor ist sehr wichtig, um zu verstehen, wie Sie Ihren Cassandra-Cluster skalieren, wenn Ihre Anwendung unter hohen Lastbedingungen steht.
Sekundärindizes
Cassandra hat auch das Konzept der Sekundärindizes. In relationalen Datenbanken könnten Sie viele Indizes in einer bestimmten Tabelle haben, die Kosten für einen sekundären Index sind mit Schreiboperationen verbunden, nicht für Leseoperationen. In Cassandra ist das nicht wahr.
Sekundärindizes in Cassandra können nützlich und verlockend sein, wenn sich Ihr Datenmodell geändert hat und Sie basierend auf einer neuen Spalte abfragen müssen.
Auf diese Weise können Sie mit einem sekundären Index eine Abfrage wie diese ausführen:
SELECT * FROM my_table WHERE SECONDARY_INDEX = ‘Wert’;
Das Problem bei der Verwendung eines Sekundärindex
Stellen Sie sich das Szenario vor: Sie befinden sich in einem Blackfriday / CyberMonday und Ihr Cassandra-Cluster leidet unter Spitzenereignissen, und Sie müssen mehr Knoten hinzufügen, um Ihre Datenbank zu skalieren, Ihren Datenverkehr besser auszugleichen und … zu überleben. Gut, oder?
Normalerweise ist dies eine normale Situation in einer hochskalierbaren Anwendung. Aber was ist, wenn Ihre Anwendung Abfragen mit einem sekundären Index ausführt?
Ja, du hast den Punkt verstanden.
Denken Sie daran, als ich sagte, dass Cassandra Daten in einem Ring mit dem Partitionsschlüssel verteilt? Dies geschieht bereits, aber das Problem besteht darin, dass Sie einen sekundären Index in Ihre Abfrage einführen. Sekundäre Indizes sind NICHT TEIL eines Partitionsschlüssels, und Cassandra weiß, wo sich Ihre Daten über den Partitionsschlüssel befinden. Wenn Sie eine Abfrage ausführen, die diese Art von Index verwendet, sucht Cassandra nach jedem Knoten in Ihrem Ring, der versucht, Ihre Abfrage zu erfüllen.
Reales Szenario
Während eines Blackfriday waren unsere Anwendungen stark belastet. Viele und viele Kunden, die von den enormen Rabatten eines Blackfriday-Events profitieren möchten.
Wir haben uns unser APM angesehen und die gesamte Analyse führte uns zu unserer Persistenz, in diesem Fall einer Cassandra-Datenbank. Wir haben lange Latenzzeiten, aber nicht für jede Anfrage, nur für einige.
Um die Dinge wieder auf den Normalzustand zu bringen, bestand unser erstes Manöver darin, unserem Cassandra-Cluster weitere Knoten hinzuzufügen.
Wir haben hinzugefügt und wir leiden immer noch unter Latenzproblemen. Die Frage war: Warum passiert das immer noch?
Wir haben uns geirrt. Es war eine vereinfachende Schlussfolgerung und wir haben uns nicht um ein sehr wichtiges Detail gekümmert: Dieses Verhalten trat nicht in allen Anfragen auf, sondern in einigen von ihnen.
Wenn Sie über den Sekundärindex nachgedacht haben, Bingo! Das war genau das Problem.
Das Hinzufügen von Knoten würde das Problem niemals lösen, da das Problem nicht mit allen Abfragen zusammenhängt, die in der Datenbank eintreffen. Es war eine Pareto-Sache.
Detaillierung des Problems und wie wir es beheben
Einen Moment vor dem Blackfriday-Ereignis mussten wir unser Datenmodell ändern. Wir regionalisierten unsere Anwendung und die Region des Kunden begann für uns eine wichtige Sache zu sein, wir mussten Daten basierend auf einem Produkt ODER einer Region abfragen.
Wenn wir zurückblicken und die Punkte verbinden, konnten wir feststellen, dass wir bei der Implementierung sehr wertvoll waren, da wir dieses neue Verhalten nicht nur in der API-Schicht (neuer Abfrageparameter) widerspiegeln wollten, sondern auch in der Art und Weise, wie wir auf die Daten in Cassandra zugegriffen haben.
Und warum waren wir so kostbar? Denn selbst wenn man bedenkt, dass sich unsere Abfragezeit nicht so stark erhöht hat, haben wir die Änderung vorgenommen.
Diese Implementierung hat nicht nur unsere Abfragezeit durch die Verwendung eines sekundären Index erhöht, sondern auch mehr Probleme verursacht, als wir die Infrastruktur von Cassandra skaliert haben. Als wir mehr Knoten in unserem Cluster hinzufügten, bedeutete dies, dass mehr Knoten nachschlagen mussten, um die Daten zu finden, sodass das Problem exponentiell zunahm.
Um das Problem zu beheben, haben wir die Anzahl der Knoten, die wir zuvor hatten, zurückgenommen und den Replikationsfaktor für die Mehrheit unserer Knoten im Cluster erhöht.
Wir haben auch unsere Lesekonsistenz geändert, um weniger konsistent zu sein. Wir haben *QUORUM verwendet und stattdessen zu EINEM gewechselt. Dies hat uns geholfen, die Last in Knoten zu senken.
Da wir unsere Anwendungen Tage vor dem Ereignis eingefroren hatten, wussten wir, dass wir keine neuen Daten (Schreibvorgänge) hatten und die Daten in ihrem aktuellen Zustand konsistent sein würden.
Die Tage danach und die DB-Modelllösung
Als Teil der endgültigen Lösung mussten wir unser Datenbankmodell (neu) überdenken und die Änderungen, die wir als Minderungspfad während des Ereignisses vorgenommen hatten, rückgängig machen.
Vor dem Ereignis haben wir die Produkt-ID (PID) als Partitionsschlüssel verwendet, was eine gute Entscheidung war, da die PID aufgrund ihrer Art, eine fortlaufende Nummer (hohe Kardinalität) zu sein, gute Attribute als PK aufweist und auf diese Weise die Daten gleichmäßig im Cluster verteilt.
Über das neue Feld “Region” nutzen wir den Datentyp Cassandra collections und haben für jede Region eine Karte als Spalte in unserer Produkttabelle verwendet.
Sekundärindizes sind immer eine schlechte Idee?
Die kurze Antwort lautet nein.
Etwas besser erklärt, gibt es in Cassandra zwei Arten von Indizes: lokale und globale Indizes.
Ein lokaler Index ist, wie der Name schon sagt, eine Art Index, der nur lokal, also in einem Knoten, existiert. Wenn Sie einen Sekundärindex erstellen, erstellt Cassandra eine neue (ausgeblendete) Tabelle, in der der Sekundärindex zu einem Primärschlüssel in dieser Tabelle wird. Die Sichtbarkeit dieser neuen Tabelle bezieht sich auf einen Knoten, nicht auf einen Ring (Cluster). Dies ist der Fall bei sekundären Indizes.
Andererseits hat ein globaler Index eine Ringsichtbarkeit durch seinen Partitionsschlüssel, sodass Cassandra über diesen Partitionsschlüssel weiß, wo sich Ihre Daten in einem Ring befinden.
Sekundärindizes können eine Alternative sein, wenn Sie in einer Abfrage sowohl Primär- als auch Sekundärindizes haben. In diesem Fall weiß Cassandra über den Partitionsschlüssel, wo sich Ihre Daten befinden (welcher Knoten), und sucht dann nach der lokalen Tabelle in dem Knoten, der auf die (lokalen) Sekundärindizes verweist.
Es gibt auch einige andere Nuancen bei Sekundärindizes, die hier sehr gut erklärt werden, aber die beste Vorgehensweise besteht darin, sie zu vermeiden, indem Sie Ihr Datenmodell denormalisieren.