
Mówi się, że w życiu wszystko warto spróbować przynajmniej raz. A jeśli jesteś przyzwyczajony do pracy z relacyjnymi bazami danych, powinieneś nawiązać praktyczny kontakt z NoSQL chociażby dla ogólnego rozwoju. Obecnie, z powodu szybkiego rozwoju tej technologii, istnieje wiele kontrowersyjnych opinii i gorących sporów na ten temat, co szczególnie podsyca zainteresowanie.
Jeśli zgłębić istotę wszystkich tych sporów, można zauważyć, że wynikają one z niewłaściwego podejścia. Ci, którzy używają baz NoSQL tam, gdzie są one potrzebne, są zadowoleni i czerpią wszystkie korzyści z tego rozwiązania. Natomiast eksperymentatorzy, którzy polegają na tej technologii jak na panaceum tam, gdzie nie ma dla niej zastosowania, doświadczają rozczarowania, tracąc mocne strony relacyjnych baz bez zyskiwania znaczących korzyści.
Opowiem o naszym doświadczeniu w implementacji rozwiązania opartego na bazie danych Cassandra: z czym musieliśmy się zmierzyć, jak wyszliśmy z trudnych sytuacji, czy udało nam się zyskać na używaniu NoSQL i gdzie musieliśmy zainwestować dodatkowe wysiłki/środki.
Początkowym zadaniem było zbudowanie systemu rejestrującego połączenia w pewnym magazynie.
Zasada działania systemu jest następująca. Na wejściu przychodzą pliki o określonej strukturze, opisującej strukturę połączenia. Następnie aplikacja zapewnia zapis tej struktury w odpowiednich kolumnach. W dalszej perspektywie zapisane połączenia są wykorzystywane do wyświetlania informacji o zużyciu ruchu dla abonentów (naliczenia, połączenia, historia salda).

Dlaczego wybraliśmy Cassandrę, jest zupełnie jasne — działa jak karabin maszynowy, jest łatwo skalowalna, odporna na awarie.
Oto, co dało nam doświadczenie
Tak, awaria węzła to nie tragedia. W tym tkwi esencja odporności na awarie Cassandry. Ale węzeł może być żywy i jednocześnie zacząć obniżać wydajność. Jak się okazało, od razu wpływa to na wydajność całego klastra.
Cassandra nie zapewni wsparcia tam, gdzie Oracle ratował swoimi ograniczeniami. I jeśli autor aplikacji nie zrozumiał tego wcześniej, to przychodzący duplikat dla Cassandry nie jest bynajmniej gorszy od oryginału. Skoro przyszedł, wstawimy go.
Bezpłatna Cassandra „z pudełka” nagle nie przypadła do gustu bezpieczeństwa informacji: Brak logowania działań użytkowników oraz podziału uprawnień.. Informacje o połączeniach odnoszą się do danych osobowych, co oznacza, że wszystkie próby ich zapytania lub zmiany muszą być rejestrowane z możliwością późniejszej audytacji. Należy również zdać sobie sprawę z konieczności rozdzielenia uprawnień na różne poziomy dla różnych użytkowników. Prosty inżynier operacyjny i superadministrator, który ma swobodę usunięcia całego keyspace, to różne role, różna odpowiedzialność i kompetencje. Bez takiego rozdzielenia uprawnień wartość i integralność danych zostaną szybko poddane wątpliwości, szybciej niż przy poziomie spójności ANY.
Nie uwzględniliśmy, że w przypadku połączeń potrzebna jest zarówno poważna analityka, jak i okresowe próbkowanie według różnych kryteriów. Ponieważ wybrane rekordy mają być później usuwane i nadpisywane (w ramach zadania musimy wspierać proces aktualizacji danych w przypadku błędnie dostarczonych informacji), Cassandra nie jest tutaj pomocna. Cassandra, jak skarbonka – łatwo jest do niej wkładać dane, ale nie da się ich przeliczyć.
Napotkaliśmy problem z transferem danych do stref testowych. (5 nodów w teście przeciwko 20 w produkcji). W takim wypadku nie można użyć zrzutu.
Problem z aktualizacją schematu danych aplikacji, która zapisuje do Cassandry. Roll-back spowoduje ogromną ilość nagrobków, co w nieprzewidywalny sposób może obniżyć wydajność.. Cassandra jest zoptymalizowana pod kątem zapisu i przed zapisaniem nie myśli zbyt wiele. Każda operacja na istniejących danych jest również zapisem. Tzn. usuwając zbędne, po prostu generujemy jeszcze więcej zapisów, a tylko część z nich będzie oznaczona nagrobkami.
Timeouty podczas wstawiania. Cassandra jest wspaniała w zapisie, ale czasami napływający strumień może ją znacznie zaskoczyć.. Dzieje się tak, gdy aplikacja zaczyna powtarzać kilka zapisów, które nie mogą być wstawione z jakiegoś powodu. I potrzebujemy prawdziwego DBA, który będzie monitorował gc.log, logi systemowe i debugowe pod kątem wolnych zapytań oraz metryk dotyczących pending compaction.
Kilka centrów danych w klastrze. Skąd czytać i gdzie pisać?
Czy można podzielić na odczyt i zapis? A jeśli tak, to czy bliżej aplikacji powinien być DC do zapisu, czy odczytu? Czy nie dojdzie do rzeczywistych problemów z podziałem, jeśli źle wybierzemy poziom spójności? Jest tak wiele pytań, wiele niezbadanych ustawień i możliwości, które chciałoby się przetestować.
Jak rozwiązaliśmy to
Aby węzeł się nie zawieszał, wyłączyliśmy SWAP. I teraz, gdy brakuje pamięci, węzeł powinien się wyłączyć, zamiast generować długie przerwy GC.
Zatem, nie polegamy już na logice w DB. Programiści aplikacji przystosowują się i zaczynają aktywnie zabezpieczać się w swoim kodzie. Idealne, wyraźne oddzielenie przechowywania i przetwarzania danych.
Zakupiliśmy wsparcie od DataStax. Rozwój wersji pudełkowej Cassandry został już wstrzymany (ostatni commit w lutym 2018 roku). Jednocześnie, Datastax oferuje doskonałą usługę oraz dużą ilość dopracowanych i dostosowanych do istniejących systemów rozwiązań.
Chciałbym również zauważyć, że Cassandra nie jest zbyt wygodna do zapytań selekcyjnych. Oczywiście, CQL to ogromny krok naprzód dla użytkowników (w porównaniu z Thrift). Ale jeśli masz całe działy, które przyzwyczaiły się do wygodnych złączeń, swobodnej filtracji po dowolnym polu i możliwości optymalizacji zapytań, a te działy pracują nad tym, aby zamykać roszczenia i awarie, to rozwiązanie w Cassandra jawi im się jako wrogie i głupie. Zaczęliśmy zastanawiać się, jak nasi koledzy mogą wykonywać zapytania.
Rozważaliśmy dwie opcje. W pierwszej opcji zapisujemy wywołania nie tylko w C*, ale także w archiwalnej bazie danych Oracle. Tylko w przeciwieństwie do C*, w tej bazie przechowywane są wywołania tylko za bieżący miesiąc (wystarczająca głębokość przechowywania wywołań dla przypadków przetargowych). Od razu dostrzegłem następujący problem: jeśli zapisujemy synchronizacyjnie, to tracimy wszystkie zalety C*, związane z szybkim wstawianiem; jeśli asynchronicznie – nie ma gwarancji, że wszystkie potrzebne wywołania trafią do Oracle. Jednym z plusów był jednak duży: do użytku pozostaje ten sam znany PL/SQL Developer, tzn. prawie realizujemy wzorzec „Fasada”. Alternatywna opcja. Realizujemy mechanizm, który eksportuje wywołania z C*, ściąga jakieś dane do wzbogacenia z odpowiednich tabel w Oracle, łączy uzyskane zestawy i daje nam uzyskany rezultat, który później jakoś wykorzystujemy (odtwarzamy, powtarzamy, analizujemy, podziwiamy). Minusy: proces wydaje się dość wieloetapowy, a poza tym brakuje interfejsu dla pracowników eksploatacji.
Ostatecznie jednak zdecydowaliśmy się na drugą opcję. Do wybierania z różnych zbiorów użyliśmy Apache Spark. Istota mechanizmu sprowadzała się do kodu w Javie, który na podstawie wskazanych kluczy (abonent, czas wywołania – klucze sekcji) wyciąga dane z C*, a także potrzebne dane do wzbogacenia z każdej innej bazy danych. Następnie łączy je w swojej pamięci i wyprowadza wynik do tabeli wynikowej. Nad sparkiem stworzyliśmy interfejs webowy i okazało się to całkiem odpowiednie do eksploatacji.

Podczas rozwiązania problemu z aktualizacją danych, zespół ponownie rozważał kilka sposobów rozwiązania. Zarówno transfer przez Sstloader, jak i opcja z podziałem klastra w strefie testowej na dwie części, z których każda naprzemiennie łączy się z klasterem produkcyjnym, zasilając się od niego. Podczas aktualizacji testów planowano zmieniać ich miejscami: ta część, która pracowała w teście, jest czyszczona i przenoszona do produkcji, podczas gdy druga zaczyna pracować z danymi oddzielnie. Jednak po ponownym przemyśleniu, bardziej racjonalnie oceniliśmy dane, które warto przenieść, i zrozumieliśmy, że same wywołania – to niespójna jednostka dla testów, szybko generowana w przypadku potrzeby, a zestaw danych produkcyjnych nie ma wartości do przeniesienia do testów. Istnieje kilka obiektów-magazynów, które warto przenieść, ale to dosłownie kilka tabel, które nie są zbyt ciężkie. Dlatego w ramach rozwiązania ponownie pomógł nam Spark, dzięki któremu napisaliśmy i zaczęliśmy aktywnie używać skryptu do transferu danych między tabelami produkcji-test.
Nasza obecna polityka wdrożeń pozwala nam działać bez rollbacków. Przed produkcją odbywa się obowiązkowe wdrożenie na test, gdzie błąd nie kosztuje tak dużo. W przypadku niepowodzenia zawsze można usunąć kespace i wdrożyć całą schemę od początku.
Aby zapewnić ciągłą dostępność Cassandry, potrzebny jest DBA i nie tylko on. Wszyscy, którzy pracują z aplikacją, muszą rozumieć, gdzie i jak sprawdzać aktualną sytuację oraz jak na czas diagnozować problemy. W tym celu aktywnie używamy DataStax OpsCenter (zarządzanie i monitorowanie obciążeń), metryk systemowych Cassandra Driver (liczba timeoutów dla zapisu w C*, liczba timeoutów dla odczytu z C*, maksymalna latencja itp.), monitorujemy działanie samej aplikacji, która działa z Cassandrą.
Kiedy zastanawialiśmy się nad poprzednim pytaniem, zrozumieliśmy, gdzie może kryć się nasz główny ryzyko. To są formy wyświetlania danych, które wyprowadzają dane z kilku niezależnych zapytań do magazynu. W ten sposób możemy uzyskać dość niespójne informacje. Ale ten problem byłby również aktualny, gdybyśmy pracowali tylko z jednym centrum danych. Najrozsądniej byłoby tutaj oczywiście stworzyć partię funkcji odczytu danych w zewnętrznej aplikacji, która zapewni uzyskanie danych w jednolitym czasie. Jeśli chodzi o rozdzielenie na odczyt i zapis w kwestii wydajności, zatrzymał nas ryzyko, że przy pewnej stracie połączenia między centrum danych, możemy otrzymać dwa zupełnie niespójne ze sobą klastry.
W rezultacie, na chwilę obecną zatrzymaliśmy się na poziomie spójności dla zapisu EACH_QUORUM, dla odczytu – LOCAL_QUORUM
Krótkie wrażenia i wnioski
Aby ocenić uzyskane rozwiązanie z punktu widzenia wsparcia eksploatacyjnego i perspektyw dalszego rozwoju, postanowiliśmy pomyśleć, gdzie jeszcze można zastosować takie opracowanie.
Jeśli chodzi o konkretne pomysły, to skoring danych dla programów typu "Płać, kiedy chcesz" (ładujemy w S* informacje, obliczenie w skryptach Spark), uwzględnianie reklamacji z agregacją według kierunków, przechowywanie ról i obliczanie na podstawie matrycy ról uprawnień użytkowników.
Jak widzimy, repertuar jest szeroki i różnorodny. I jeśli wybierać oboz wśród zwolenników/przeciwników NoSQL, to przyłączymy się do zwolenników, gdyż dostaliśmy swoje korzyści, i to tam, gdzie się ich spodziewaliśmy.
Nawet wariant Cassandra z pudełka pozwala na poziome skalowanie w czasie rzeczywistym, bezproblemowo rozwiązując problem zwiększenia danych w systemie. Udało nam się wydzielić w osobny kontur bardzo obciążony mechanizm do obliczania agregatów według połączeń, a także podzielić schemat i logikę aplikacji, pozbywając się złej praktyki pisania niestandardowych zadań i obiektów w samej bazie danych. Zyskaliśmy możliwość wyboru i dostosowywania, w celu przyspieszenia, na jakich centrach danych będziemy przeprowadzać obliczenia, a na których zapisywać dane, zabezpieczając się przed awariami zarówno poszczególnych węzłów, jak i całego centrum danych.
Wdrażając naszą architekturę do nowych projektów, mając już pewne doświadczenie, chcielibyśmy od razu uwzględnić opisane powyżej niuanse i uniknąć pewnych błędów oraz złagodzić niektóre ostre krawędzie, których nie udało się uniknąć na początku.
Na przykład, na bieżąco śledzić aktualizacje samej Kassandra, ponieważ wiele problemów, które napotkaliśmy, były już znane i naprawiane.
Nie umieszczać zarówno bazy danych, jak i Sparka na tych samych węzłach (lub ściśle dzielić według dopuszczalnej ilości zużycia zasobów), ponieważ Spark może zjeść więcej pamięci RAM, niż to możliwe, a my szybko napotkamy problem numer 1 z naszej listy.
Zwiększać monitoring i umiejętności eksploatacji już na etapie testowania projektu. Początkowo brać pod uwagę maksymalnie wszystkich potencjalnych użytkowników naszego rozwiązania, ponieważ to od tego będzie z czasem zależeć struktura bazy danych.
Kilka razy przeanalizować uzyskaną schemę pod kątem możliwej optymalizacji. Określić, które pola można serializować. Zrozumieć, jakie dodatkowe tabele powinniśmy stworzyć, aby najdokładniej i optymalnie uwzględnić, a następnie zwracać wymagane informacje na zapytania (na przykład, zakładając, że te same dane możemy przechowywać w różnych tabelach, uwzględniając różne podziały według różnych kryteriów, możemy znacznie zaoszczędzić czas procesora przy zapytaniach do odczytu).
Nieźle od razu przewidzieć nakładanie TTL i czyszczenie przestarzałych danych.
Podczas eksportowania danych z Cassandry logika aplikacji musi działać na zasadzie FETCH, aby nie wszystkie wiersze były ładowane do pamięci jednocześnie, ale wybierane partiami.
Warto przed przełożeniem projektu na opisaną architekturę sprawdzić odporność systemu, przeprowadzając serię testów awaryjnych, takich jak utrata danych w jednym centrum danych, odzyskiwanie uszkodzonych danych w określonym okresie, spadki sieci między centrami danych. Takie testy nie tylko pozwolą ocenić zalety i wady proponowanej architektury, ale również dadzą cenną praktykę inżynierom je przeprowadzającym, a nabyta umiejętność będzie nieoceniona, gdy awarie systemu wystąpią w produkcji.
Jeśli pracujemy z krytycznymi informacjami (takimi jak dane do przeprowadzania rozliczeń, obliczanie zaległości abonenta), warto również zwrócić uwagę na narzędzia, które pozwolą zmniejszyć ryzyko wynikające z charakterystyki systemów zarządzania bazami danych. Na przykład, warto wykorzystać narzędzie nodesync (Datastax), opracowując optymalną strategię jego użycia, aby w celu zapewnienia spójności nie generować nadmiernego obciążenia dla Cassandry i stosować je tylko do określonych tabel w określonym okresie.
Cóż, po pół roku życia z Cassandrą? Generalnie, nie ma nierozwiązanych problemów. Nie dopuściliśmy również do poważnych awarii ani utraty danych. Tak, trzeba było pomyśleć nad rekompensatą niektórych wcześniej nieprzewidzianych problemów, ale ostatecznie nie wpłynęło to znacząco na nasze rozwiązanie architektoniczne. Jeśli chcesz i nie boisz się próbować czegoś nowego, a jednocześnie nie chcesz się zbytnio rozczarować, przygotuj się na to, że nic nie jest za darmo. Trzeba będzie więcej przyglądać się, zgłębiać dokumentację i zbierać swoje indywidualne pułapki, niż w starym rozwiązaniu legacy, a żadna teoria nie wskaże z wyprzedzeniem, jakie pułapki czekają na ciebie.
Źródło: habr.com
