Cassandra. Jak nie umrzeć, gdy znasz tylko Oracle

Cześć, Habr.

Nazywam się Misha Butrimov i chciałbym opowiedzieć trochę o Cassandrze. Moja opowieść będzie przydatna dla tych, którzy nigdy nie mieli do czynienia z bazami NoSQL – ma ona wiele szczególnych cech i pułapek, o których warto wiedzieć. A jeśli widzieliście tylko Oracle lub inną relacyjną bazę, te informacje uratują wam życie.

Co jest dobre w Cassandrze? To baza danych NoSQL, zaprojektowana bez pojedynczego punktu awarii, która doskonale się skaluje. Jeśli potrzebujesz dodać kilka terabajtów dla jakiejś bazy, po prostu dodajesz węzły do pierścienia. Chcesz rozszerzyć ją o kolejny centrum danych? Dodajesz węzły do klastra. Zwiększyć przetwarzane RPS? Dodajesz węzły do klastra. W drugą stronę też działa.

Cassandra. Jak nie umrzeć, gdy znasz tylko Oracle

Na co jeszcze jest dobra? Na obsługę wielu zapytań. Ale ile to dużo? 10, 20, 30, 40 tysięcy zapytań na sekundę – to niewiele. 100 tysięcy zapytań na sekundę do zapisu – też niewiele. Są firmy, które mówiły, że utrzymują 2 mln zapytań na sekundę. Wydaje mi się, że muszą im uwierzyć.

I zasadniczo Cassandra ma jedną dużą różnicę w porównaniu do danych relacyjnych – właściwie w ogóle na nie nie wygląda. I o tym warto pamiętać.

Nie wszystko, co wygląda tak samo, działa tak samo.

Kiedyś przyszedł do mnie kolega i zapytał: „Oto SQL, język zapytań Cassandry, a w nim jest instrukcja select, jest where, jest and. Piszę litery i nie działa. Dlaczego?”. Jeśli podejdziesz do Cassandry jak do relacyjnej bazy danych, to idealny sposób na zakończenie życia brutalnym samobójstwem. I nie propaguję tego, w Rosji jest to zabronione. Po prostu zaprojektujesz coś źle.

Na przykład przychodzi do nas klient i mówi: „Zbudujmy bazę danych dla seriali, albo bazę danych dla katalogu przepisów. Będą tam potrawy z produktami albo lista seriali i aktorów w nim”. My radośnie mówimy: „Zróbmy to!”. To dwa bajty do przesłania, parę tabel i wszystko gotowe, będzie działać bardzo szybko, niezawodnie. I wszystko jest wspaniale, aż klienci nie przychodzą i nie mówią, że gospodynie domowe rozwiązują jeszcze odwrotny problem: mają listę produktów i chcą wiedzieć, które danie chcą przygotować. Jesteś martwy.

Wszystko dlatego, że Cassandra to hybrydowa baza danych: jest jednocześnie typu key value i przechowuje dane w szerokich kolumnach. Mówiąc językiem Javy lub Kotlina, można opisać to w ten sposób:

Map<RowKey, SortedMap>

To znaczy, mapa, w której znajduje się również posortowana mapa. Pierwszym kluczem do tej mapy jest Row key lub Partition key — klucz partycjonowania. Drugim kluczem, który jest kluczem do już posortowanej mapy, jest Clustering key.

Aby zilustrować rozproszoność bazy danych, narysujemy trzy węzły. Teraz trzeba zrozumieć, jak rozłożyć dane na węzłach. Ponieważ jeśli włożymy wszystko do jednego (może ich być tysiąc, dwa tysiące, pięć — ile tylko zechcesz), to nie bardzo chodzi o rozproszoność. Dlatego potrzebujemy funkcji matematycznej, która zwraca liczbę. Po prostu liczbę, długi int, który będzie mieścił się w jakimś zakresie. I jeden węzeł będzie odpowiadał za jeden zakres, drugi — za drugi, a n-ty — za n-ty.

Cassandra. Jak nie umrzeć, gdy znasz tylko Oracle

Liczba ta jest uzyskiwana za pomocą funkcji haszującej, która jest stosowana właśnie do tego, co nazywamy Partition key. To ta kolumna, która jest wskazana w dyrektywie Primary key i to ta kolumna, która będzie pierwszym i głównym kluczem mapy. Określa, na jaki węzeł trafią jakie dane. Tabela w Cassandrze jest tworzona prawie z takim samym składniowym jak w SQL:

CREATE TABLE users (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY(user_id)

)

Primary key w tym przypadku składa się z jednej kolumny, która jest również kluczem partycjonowania.

Jak ułożymy użytkowników? Część trafi na jeden węzeł, część — na drugi, a część — na trzeci. Powstaje zwykła tabela haszująca, czyli mapa, która w Pythonie jest słownikiem, a także prostą strukturą Key value, z której możemy odczytywać wszystkie wartości, czytać i zapisywać po kluczu.

Cassandra. Jak nie umrzeć, gdy znasz tylko Oracle

Select: kiedy allow filtering zamienia się w pełne skanowanie, czyli jak nie należy tego robić

Napiszmy jakiś select statement: select * from users where userid = . Wygląda to trochę jak w Oracle: piszemy select, podajemy warunki i wszystko działa, użytkownicy są pobierani. Ale jeśli na przykład wybierzemy użytkownika z określonym rokiem urodzenia, Cassandra narzeka, że nie może wykonać zapytania. Bo tak naprawdę nic nie wie o tym, jak są u nas rozdzielane dane o roku urodzenia — jako klucz jest wskazana tylko jedna kolumna. Wtedy mówi: „Dobrze, mogę nadal wykonać to zapytanie. Dodajcie allow filtering”. Dodajemy dyrektywę, wszystko działa. I w tym momencie dzieje się coś strasznego.

Kiedy testujemy na danych testowych, wszystko jest w porządku. A kiedy wykonujecie zapytanie w produkcji, gdzie mamy na przykład 4 miliony rekordów, to nie jest zbyt dobrze. Ponieważ allow filtering — to dyrektywa, która pozwala Cassandrze zebrać wszystkie dane z tej tabeli ze wszystkich węzłów, wszystkich data-centra (jeśli jest ich wiele w tym klastrze), a dopiero potem je filtrować. To analogiczne do Full Scan, i wątpię, żeby ktokolwiek był z tego zadowolony.

Gdybyśmy potrzebowali tylko użytkowników według identyfikatorów, to by nas zadowoliło. Ale czasami musimy pisać inne zapytania i nakładać inne ograniczenia na selekcję. Dlatego przypominamy sobie: to wszystko to mapa, która ma klucz partycjonowania, ale w jej wnętrzu — posortowana mapa.

I ona też ma klucz, który nazywamy Clustering Key. Ten klucz, który z kolei składa się z kolumn, które wybierzemy, za pomocą których Cassandra rozumie, jak jej dane będą fizycznie posortowane i będą leżały na każdym węźle. To znaczy, dla jakiegoś klucza partycjonowania, Clustering key opowie, jak dokładnie dane umieścić w tym drzewie, jakie miejsce zajmą.

To w rzeczywistości drzewo, tam po prostu wywoływany jest komparator, do którego przekazujemy pewien zestaw kolumn w postaci obiektu, a on też jest ustawiany w postaci wyliczenia kolumn.

CREATE TABLE users_by_year_salary_id (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY((year), salary, user_id)

Zwróć uwagę na dyrektywę Primary key, jej pierwszy argument (w naszym przypadku rok) zawsze jest kluczem partycjonowania. Może składać się z jednej lub kilku kolumn, to nie ma znaczenia. Jeśli kolumn jest kilka, należy je umieścić w nawiasach, aby preprocesor językowy zrozumiał, że to właśnie Primary key, a za nim wszystkie pozostałe kolumny — Clustering key. Będą one przesyłane w komparatorze w tej samej kolejności, w jakiej się pojawiają. To znaczy, że pierwsza kolumna ma większe znaczenie, druga — mniejsze i tak dalej. Jak piszemy dla klas danych, na przykład, pola equals: wymieniamy pola i dla nich określamy, które są większe, a które mniejsze. W Cassandrze to, powiedzmy, pola klasy danych, do której będzie stosowany zdefiniowany dla niej equals.

Ustalamy sortowanie, nakładamy ograniczenia

Należy pamiętać, że kolejność sortowania (malejąca, rosnąca, bez znaczenia) jest ustalana w tym samym momencie, gdy klucz jest tworzony, i później nie można jej zmienić. Fizycznie określa, jak będą posortowane dane i jak będą się układały. Jeśli zajdzie potrzeba zmiany Clustering key lub kolejności sortowania, trzeba będzie stworzyć nową tabelę i przelać do niej dane. Z już istniejącą tak się nie uda.

Cassandra. Jak nie umrzeć, gdy znasz tylko Oracle

Wypełniliśmy naszą tabelę użytkownikami i zauważyliśmy, że zgrupowali się najpierw według roku urodzenia, a następnie w obrębie każdej nody według wynagrodzenia i identyfikatora użytkownika. Teraz możemy selekcjonować, nakładając ograniczenia.

Ponownie pojawia się nasz działający gdzie, i, a użytkownicy przypisują się do nas, i wszystko znowu jest dobrze. Ale jeśli spróbujemy użyć tylko części Clustering key, w dodatku mniej istotnej, to Cassandra natychmiast zasygnalizuje, że nie może znaleźć w naszej mapie miejsca, gdzie ten obiekt, który ma te pola dla komparatora jako null, należy, a gdzie jest ten, który dopiero co zadaliśmy. Będę musiał ponownie pobrać wszystkie dane z tej nody i je przefiltrować. I to jest odpowiednik pełnego skanowania w ramach nody, co jest niekorzystne.

W każdej niejasnej sytuacji twórz nową tabelę

Jeśli chcemy mieć możliwość wyciągania użytkowników po ID, wieku czy zarobkach, co robić? Nic. Po prostu użyjemy dwóch tabel. Jeżeli będziemy potrzebować wyciągać użytkowników trzema różnymi sposobami — tabel będzie trzy. Minęły czasy, kiedy oszczędzaliśmy miejsce na dysku. To najtańszy zasób. Kosztuje znacznie mniej niż czas odpowiedzi, który może być zgubny dla użytkownika. Użytkownik woli otrzymać coś w sekundę, niż w 10 minut.

Wymieniamy nadmiar zajmowanego miejsca, denormalizowane dane na możliwość dobrej skalowalności, niezawodnej pracy. W końcu klaster składający się z trzech centrów danych, w każdym z nich po pięć węzłów, przy akceptowalnym poziomie zachowania danych (gdy nic nie zostanie utracone), jest w stanie przetrwać całkowitą śmierć jednego centrum danych. I jeszcze po dwa węzły w każdym z pozostałych dwóch. I dopiero po tym zaczynają się problemy. To dość dobre zabezpieczenie, kosztujące parę dodatkowych dysków SSD i procesorów. Dlatego, aby korzystać z Cassandry, która nie jest SQL, w której nie ma relacji ani kluczy obcych, należy znać kilka prostych zasad.

Projektujemy wszystko od zapytania. Najważniejsze są nie dane, ale to, jak aplikacja planuje z nimi pracować. Jeśli potrzebuje pozyskiwać różne dane w różny sposób lub te same dane na różne sposoby, musimy je umieścić w sposób wygodny dla aplikacji. W przeciwnym razie wpadniemy w pełne skanowanie i żadna zaleta Cassandry nam nie pomoże.

Denormalizacja danych to norma. Zapominamy o normalnych formach, nie mamy już baz relacyjnych. Umieścimy coś 100 razy, będzie leżało 100 razy. To wciąż tańsze niż spowolnienie działania.

Wybieramy klucze do partycjonowania tak, aby były równomiernie rozłożone. Nie chcemy, aby hash naszych kluczy trafiał w jeden wąski zakres. To znaczy, rok urodzenia w powyższym przykładzie — to zły przykład. Właściwie jest dobry, jeśli użytkownicy według roku urodzenia są normalnie rozłożeni, a zły, jeśli mowa o uczniach 5. klasy — tam nie będzie zbyt dobrze z partycjonowaniem.

Sortowanie jest wybierane tylko raz na etapie tworzenia Klucza Klasteryzacji. Jeżeli zajdzie potrzeba zmiany, będziemy musieli przelać naszą tabelę z innym kluczem.

I to, co najważniejsze: jeśli potrzebujemy 100 różnych sposobów na pozyskanie tych samych danych, będziemy mieli 100 różnych tabel.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster