niewłaściwy sposób skalowania bazy danych Cassandra, gdy istnieją indeksy drugorzędne
Cassandra jest moją ulubioną (nie zarządzaną) bazą danych z wielu powodów: nie ma pojedynczego punktu awarii (SPoF), obsługuje wiele regionów, jest dobra do odczytu i zapisu operacji, elastyczna w zakresie poziomów spójności odczytu i zapisu, skaluje liniowo i nie jest zbyt skomplikowana do zarządzania codziennymi operacjami.
jak każda baza danych, powinieneś używać Cassandry w oparciu o wzorce dostępu do danych, więc jeśli potrzebujesz elastycznej bazy danych do zapytań ad-hoc lub wystarczająco elastycznej do ciągłych zmian modelu bazy danych, powinieneś rozważyć inne opcje.
Cassandra jest bazą danych zorientowaną na kolumny i jest naprawdę potężna, gdy masz już zdefiniowane zapytania do danych. Datastax, firma wspierająca Cassandrę, zaleca, aby zacząć od zaprojektowania zapytań, a następnie modelu danych w Cassandrze. Pomimo faktu struktury Kolumnowej, Cassandra obsługuje wiele struktur danych jako typ kolumny(kolumn), takich jak Mapy.
Cassandra jest bazą danych klucza podstawowego, co oznacza, że Twoje dane są przechowywane i organizowane wokół klastra na podstawie wartości skrótu (klucza partycji) klucza podstawowego. Dla tabel, które mają więcej niż jeden PK, Cassandra bierze pod uwagę tylko pierwszą część PK jako klucz partycji. Zobacz więcej o kluczach kompozytowych tutaj.
aby było bardziej jasne, wróćmy do jednej z najważniejszych cech Cassandra DB: to architektura i fakt, że nie ma SPoF.
klaster Cassandra składa się z węzłów (3 lub więcej) i te węzły razem tworzą pierścień węzłów:
każdy węzeł w klastrze Cassandry działa “niezależnie”, ale różne węzły mogą przechowywać te same dane odpowiednio do konfiguracji współczynnika replikacji (RF) skonfigurowanej dla klastra.
aby wiedzieć, gdzie (który węzeł) dane są przechowywane, Cassandra używa wartości skrótu (tokena) obliczonej za pomocą spójnej funkcji skrótu wykorzystującej kolumnę PK danej tabeli.
kiedy uruchamiasz zapytanie, węzeł koordynatora (Zwykle najbliższy z instancji aplikacji) będzie szukał węzłów w pierścieniu, które mają Twoje dane, w ten sposób, jeśli jeden węzeł jest z jakiegoś powodu wyłączony, inny węzeł może służyć Twoim danym (RF ≥2). To jest magia w podejściu bez mistrza, gdzie każdy węzeł w pierścieniu jest równy pod względem odczytu i zapisu.
ta koncepcja dotycząca PK i współczynnika replikacji jest bardzo ważna, aby zrozumieć, jak skalować klaster Cassandra, gdy aplikacja jest w warunkach dużego obciążenia.
indeksy wtórne
W relacyjnych bazach danych można mieć wiele indeksów w danej tabeli, koszt posiadania indeksu wtórnego jest związany z operacjami zapisu, a nie z operacjami odczytu. W Cassandrze to nieprawda.
wtórne indeksy w Cassandrze mogą być przydatne i kuszące, gdy twój model Danych się zmienił i musisz zapytać na podstawie nowej kolumny.
w ten sposób, z indeksem wtórnym, można uruchomić zapytanie w ten sposób:
SELECT * FROM my_table WHERE SECONDARY_INDEX = ‘value’;
problem z użyciem indeksu wtórnego
wyobraź sobie scenariusz: jesteś w Blackfriday / CyberMonday, a Twój klaster Cassandra cierpi na szczytowe zdarzenia i musisz dodać więcej węzłów, aby skalować bazę danych, równoważąc lepszy ruch i… przetrwać. Dobrze, prawda?
Normalnie jest to normalna sytuacja w wysoce skalowalnej aplikacji. Ale co jeśli Twoja aplikacja uruchamia zapytania przy użyciu indeksu wtórnego?
tak, masz rację.
pamiętasz, jak powiedziałem, że Cassandra rozprowadza dane w pierścieniu za pomocą klucza partycji? To już się dzieje, ale problem polega na wprowadzeniu wtórnego indeksu w zapytaniu. Drugorzędne indeksy nie są częścią klucza partycji, a Cassandra wie o tym, gdzie Twoje dane są przechowywane przez klucz partycji. Kiedy uruchamiasz zapytanie, które używa tego rodzaju indeksu, Cassandra szuka każdego węzła w pierścieniu, próbując zaspokoić Twoje zapytanie.
prawdziwy Scenariusz
podczas Blackfriday nasze aplikacje były z dużymi obciążeniami. Wielu klientów chcących skorzystać z ogromnych rabatów oferowanych przez Blackfriday event.
przyjrzeliśmy się naszemu APM i wszystkie analizy doprowadziły nas do naszej wytrwałości, w tym przypadku Cassandra DB. Mamy długie okresy opóźnienia, ale nie na każde żądanie, tylko na niektóre.
próbując przywrócić wszystko do normalnego stanu, naszym pierwszym manewrem było dodanie kolejnych węzłów do naszego klastra Cassandry.
dodaliśmy i nadal mamy problemy z opóźnieniami. Pytanie brzmiało: dlaczego tak się dzieje?
myliliśmy się. To był uproszczony wniosek i nie zadbaliśmy o jeden bardzo ważny szczegół: takie zachowanie działo się nie we wszystkich prośbach, ale w niektórych z nich.
jeśli myślisz o indeksie wtórnym, bingo! To był właśnie problem.
dodanie węzłów nigdy nie rozwiąże problemu, ponieważ problem nie był związany ze wszystkimi zapytaniami przychodzącymi do bazy danych, problem był w niektórych, a te były prawdziwe, obniżając wydajność bazy danych. To było Pareto.
opisując problem i sposób jego złagodzenia
na chwilę przed wydarzeniem Blackfriday musieliśmy zmienić nasz model danych. Regionalizowaliśmy naszą aplikację i region klienta zaczął być dla nas ważny, musieliśmy wyszukiwać dane w oparciu o produkt lub region.
patrząc wstecz i łącząc punkty, mogliśmy zdać sobie sprawę, że byliśmy bardzo cenni w implementacji, ponieważ chcieliśmy odzwierciedlić to nowe zachowanie nie tylko w warstwie API (nowy param zapytań), ale także w sposobie, w jaki uzyskaliśmy dostęp do danych w Cassandrze.
i dlaczego byliśmy tacy cenni? Ponieważ nawet biorąc pod uwagę, że nasz czas zapytań nie zwiększył się tak bardzo, dokonaliśmy zmiany.
Ta implementacja nie tylko zwiększyła nasz czas zapytań poprzez użycie drugorzędnego indeksu, ale także wygenerowała więcej problemów zgodnie ze skalowaniem infrastruktury Cassandry. Ponieważ dodaliśmy więcej węzłów w naszym klastrze, oznaczało to, że więcej węzłów trzeba było szukać, aby znaleźć dane, więc problem wzrastał wykładniczo.
aby złagodzić ten problem, cofnęliśmy wcześniej liczbę węzłów i zwiększyliśmy współczynnik replikacji dla większości węzłów w klastrze.
zmieniliśmy również poziom spójności odczytu, aby był mniej spójny. Używaliśmy *QUORUM i zamiast tego zmieniliśmy na jeden. Pomogło nam to obniżyć obciążenie węzłów.
ponieważ zamroziliśmy nasze aplikacje na kilka dni przed wydarzeniem, wiedzieliśmy, że nie mamy nowych danych (operacji zapisu) i dane będą spójne w ich aktualnym stanie.
the days after I rozwiązanie modelu DB
w ramach Ostatecznego Rozwiązania musieliśmy (ponownie)przemyśleć nasz model bazy danych i cofnąć zmiany, które zrobiliśmy jako ścieżkę łagodzenia podczas wydarzenia.
przed wydarzeniem używaliśmy identyfikatora produktu (PID) jako klucza partycji, co było dobrą decyzją, ponieważ PID ma dobre atrybuty, aby być PK ze względu na swoją naturę bycia numerem sekwencyjnym (wysoka cardinalność), a tym samym równomierne rozprowadzanie danych po klastrze.
o nowym polu “region” wykorzystaliśmy typ danych kolekcji Cassandra i wykorzystaliśmy mapę dla każdego regionu jako kolumnę w naszej tabeli produktów.
indeksy wtórne to zawsze zły pomysł?
krótka odpowiedź brzmi nie.
wyjaśniając nieco lepiej, istnieją dwa rodzaje indeksów w Cassandrze: indeksy lokalne i globalne.
indeks lokalny jak sama nazwa mówi jest rodzajem indeksu, który istnieje tylko lokalnie, to znaczy w węźle. Podczas tworzenia indeksu wtórnego Cassandra tworzy nową (ukrytą) tabelę, w której drugorzędny staje się kluczem podstawowym w tej tabeli. Widoczność tej nowej tabeli jest w kategoriach węzła, a nie pierścienia (klastra). Tak jest w przypadku drugorzędnych indeksów.
z drugiej strony, indeks globalny ma widoczność pierścienia przez jego klucz partycji, więc Cassandra wie, gdzie Twoje dane są w pierścieniu przez ten klucz partycji.
indeksy wtórne mogą być alternatywą, jeśli w zapytaniu masz zarówno indeksy pierwotne, jak i wtórne. W takim przypadku Cassandra wie, gdzie znajdują się Twoje dane (który węzeł) za pośrednictwem klucza partycji, a następnie wyszukuje lokalną tabelę w węźle, która odnosi się do (lokalnych) drugorzędnych indeksów.
istnieją również inne niuanse dotyczące indeksów wtórnych, które są bardzo dobrze wyjaśnione tutaj, ale najlepszą praktyką jest unikanie ich poprzez denormalizację modelu danych.