HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Następna konferencja HighLoad++ odbędzie się 6 i 7 kwietnia 2020 roku w Petersburgu.
Szczegóły i bilety pod tym linkiem. HighLoad++ Siberia 2019. Sala „Krasnojarsk”. 25 czerwca, 12:00. Streszczenia i prezentacja.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Czasami praktyczne wymagania kolidują z teorią, w której nie uwzględnione są ważne aspekty dla produktu komercyjnego. W tym wystąpieniu przedstawiony zostanie proces wyboru i łączenia różnych podejść do tworzenia komponentów Causal consistency na podstawie badań akademickich zgodnie z wymaganiami produktu komercyjnego. Słuchacze dowiedzą się o istniejących teoretycznych podejściach do zegarów logicznych, śledzenia zależności, bezpieczeństwa systemów, synchronizacji zegara i dlaczego MongoDB zatrzymało się na takich, a nie innych rozwiązaniach.

Michał Tyłeniew (dalej – MT): – Będę mówił o Causal consistency – to funkcjonalność, nad którą pracowaliśmy w MongoDB. Pracuję w grupie systemów rozproszonych, stworzyliśmy to około dwa lata temu.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

W procesie musiałem zapoznać się z dużą ilością badań akademickich, ponieważ ta funkcjonalność jest dość dobrze zbadana. Okazało się, że żaden z artykułów nie wpisuje się w to, co jest wymagane w produkcji baz danych, z uwagi na dość specyficzne wymagania, które występują prawdopodobnie w każdej aplikacji produkcyjnej.

Będę opowiadać o tym, jak jako konsumenci badań akademickich przygotowujemy coś, co możemy później przedstawić naszym użytkownikom jako gotowe danie, z którego łatwo i bezpiecznie korzystać.

Przyczynowa zgodność (Causal consistency). Zdefiniujmy pojęcia

Na początek chciałbym w ogólnych zarysach powiedzieć, czym jest Causal consistency. Są dwie postacie – Leonard i Penny (serial „Teoria wielkiego podrywu”):

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Załóżmy, że Penny jest w Europie, a Leonard chce zrobić dla niej jakąś niespodziankę, imprezę. I nie wymyśla nic lepszego, jak usunąć ją z listy znajomych, wysłać wszystkim znajomym aktualizację na feed: „Sprawmy radość Penny!” (ona jest w Europie, podczas gdy śpi, nie widzi tego wszystkiego i nie może tego zobaczyć, ponieważ jej tam nie ma). W ostatecznym momencie usuwa ten post, wymazuje z „Fida” i przywraca dostęp, żeby niczego nie zauważyła i żeby nie było skandalu.
To wszystko jest w porządku, ale załóżmy, że system jest rozproszony i wydarzenia potoczyły się nieco inaczej. Może na przykład zdarzyć się, że ograniczenie dostępu Penny nastąpiło po tym, jak ten post się pojawił, jeśli wydarzenia nie są ze sobą powiązane przyczynowo. W rzeczywistości to przykład sytuacji, w której wymagana jest spójność przyczynowa, aby zrealizować funkcję biznesową (w tym przypadku).

W rzeczywistości to dość nietrywialne właściwości baz danych – bardzo niewiele z nich je wspiera. Przejdźmy do modeli.

Modele spójności (Consistency Models)

Czym tak naprawdę jest model spójności w bazach danych? To pewne gwarancje, jakie rozproszony system daje odnośnie do tego, jakie dane i w jakiej kolejności klient może otrzymać.

W zasadzie wszystkie modele spójności sprowadzają się do tego, jak bardzo rozproszony system przypomina system, który działa, na przykład, na jednym węźle na laptopie. I jak bardzo system działający na tysiącach geograficznie rozproszonych węzłów przypomina laptopa, w którym wszystkie te właściwości są automatycznie spełnione.

Dlatego modele spójności stosuje się tylko do systemów rozproszonych. Wszystkie systemy, które wcześniej istniały i działały na jednym poziomie pionowej skalowalności, takich problemów nie doświadczały. Była tam jedna pamięć podręczna, z której wszystko zawsze było odczytywane.

Model Strong

Właściwie, pierwszym modelem jest Strong (lub jak często nazywa się go linearną spójnością). To model spójności, który zapewnia, że każda zmiana, gdy tylko uzyska potwierdzenie, staje się widoczna dla wszystkich użytkowników systemu.

To tworzy globalny porządek wszystkich zdarzeń w bazie danych. To bardzo silna właściwość spójności, a jego implementacja jest kosztowna. Niemniej jednak jest ona dobrze wspierana. Po prostu jest bardzo droga i wolna – rzadko się z niej korzysta. Nazywa się to liniową spójnością.

Jest jeszcze jedna, silniejsza właściwość, która jest wspierana w „Spannerze” – nazywa się spójnością zewnętrzną. Omówimy ją później.

Przyczynowa

Następujące to Causal, o którym właśnie mówiłem. Pomiędzy Strong a Causal istnieje jeszcze kilka podpoziomów, o których nie będę wspominać, ale wszystkie sprowadzają się do Causal. To ważny model, ponieważ jest najsilniejszy ze wszystkich modeli, zapewniając najsilniejszą spójność w obecności sieci lub partitioningów.

Causals to sytuacja, w której zdarzenia są powiązane relacją przyczynowo-skutkową. Często postrzega się je jako Read your on rights z perspektywy klienta. Jeśli klient zauważył jakieś wartości, nie może zobaczyć wartości, które były w przeszłości. Zaczyna widzieć odczyty prefiksowe. To wszystko sprowadza się do jednego i tego samego.
Causals jako model spójności to częściowe uporządkowanie zdarzeń na serwerze, w którym zdarzenia ze wszystkich klientów są obserwowane w tej samej kolejności. W tym przypadku – Leonard i Penny.

Eventual

Trzeci model to Eventual Consistency. To model, który wspiera absolutnie wszystkie systemy rozproszone, minimalny model, który w ogóle ma sens. Oznacza to, że kiedy zachodzą pewne zmiany w danych, w pewnym momencie stają się one spójne.

W tym momencie nic nie mówi, w przeciwnym razie przekształciłby się w External Consistency – to byłaby zupełnie inna historia. Niemniej jednak to bardzo popularny model, najczęściej spotykany. Domyślnie wszyscy użytkownicy systemów rozproszonych korzystają właśnie z Eventual Consistency.

Chcę podać kilka porównawczych przykładów:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Co oznaczają te strzałki?

  • Opoznienie. Przy zwiększeniu mocy spójności staje się ono większe z oczywistych powodów: należy wykonać więcej zapisów, uzyskać potwierdzenie od wszystkich hostów i węzłów, które uczestniczą w klastrze, że dane tam już są. W związku z tym w Eventual Consistency odpowiedź jest najszybsza, ponieważ przeważnie można nawet w pamięci przykomitować i tego będzie w zasadzie wystarczające.
  • Dostępność. Jeśli to zrozumieć jako możliwość systemu do odpowiedzi przy istnieniu przerw w sieci, partitioningach, lub jakichkolwiek awariach – odporność na awarie wzrasta przy zmniejszeniu modelu spójności, ponieważ wystarczy, że jeden host działa i przy tym dostarcza jakieś dane. Eventual Consistency w ogóle nic nie gwarantuje w zakresie danych – to mogą być cokolwiek.
  • Anomalie. Jednocześnie oczywiście zwiększa się liczba anomalii. W przypadku Silnej Spójności nie powinny one właściwie w ogóle występować, natomiast w przypadku Spójności Ostatecznej mogą być dowolne. Pojawia się pytanie: dlaczego ludzie wybierają Spójność Ostateczną, skoro zawiera ona anomalie? Odpowiedź jest taka, że modele Spójności Ostatecznej są odpowiednie, a anomalie występują na przykład przez krótki czas; istnieje możliwość korzystania z mastera do odczytu i dość spójnego czytania danych; często można wykorzystać silne modele spójności. W praktyce to działa, a liczba anomalii jest często ograniczona czasowo.

Teoria CAP

Co myślisz, gdy widzisz słowa spójność, dostępność? Dokładnie – teoria CAP! Chcę teraz obalić mit... To nie ja – to Martin Kleppmann, który napisał znakomity artykuł, znakomitą książkę.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Teoria CAP to zasada sformułowana w latach 2000, dotycząca tego, że Spójność, Dostępność, Partycje: wybierz dowolne dwie, a nie można wybrać trzech. To był pewien zasadniczy koncept. Został on udowodniony jako twierdzenie kilka lat później, dokonali tego Gilbert i Lynch. Następnie zaczęto go stosować jako mantrę – systemy zaczęły dzielić się na CA, CP, AP i tak dalej.

To twierdzenie zostało udowodnione naprawdę w takich przypadkach... Po pierwsze, dostępność nie była rozumiana jako wartość ciągła od zera do stu (0 – system "martwy", 100 – szybko odpowiada; przyzwyczailiśmy się tak na to patrzeć), ale jako właściwość algorytmu, która gwarantuje, że przy wszystkich jego wykonaniach zwraca dane.

Nie ma tam ani słowa o czasie odpowiedzi! Jest algorytm, który zwraca dane po 100 latach – doskonały algorytm dostępny, który jest częścią teorii CAP.
Po drugie: twierdzenie było dowodzone dla zmian w wartościach tego samego klucza, a te zmiany są linią resizable. Oznacza to, że w rzeczywistości prawie nie są one wykorzystywane, ponieważ istnieją inne modele Spójności Ostatecznej, Spójności Silnej (może być).

O co w tym wszystkim chodzi? O to, że teoria CAP w takiej formie, w jakiej została dowiedziona, praktycznie nie ma zastosowania, rzadko jest stosowana. W formie teoretycznej w jakiś sposób wszystko ogranicza. Powstaje pewna zasada, która jest intuicyjnie prawdziwa, ale w ogóle nie zostało to udowodnione.

Spójność przyczynowa – najsilniejszy model

Obecnie można uzyskać trzy rzeczy: spójność, dostępność dzięki partycjom. W szczególności spójność przyczynowa to najsilniejszy model spójności, który działa nawet w przypadku partycji (przerwy w sieci). Dlatego jest tak interesujący i dlatego zajęliśmy się tym tematem.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Po pierwsze, upraszcza pracę programistów aplikacji. W szczególności zapewnia dużą pomoc ze strony serwera: gdy wszystkie zapisane dane z jednego klienta są gwarantowane w takiej samej kolejności na innym kliencie. Po drugie, wytrzymuje partycje.

Wewnętrzna kuchnia MongoDB

Pamiętając o lunchu, przechodzimy do kuchni. Opowiem o modelu systemu, a konkretnie – czym jest MongoDB dla tych, którzy pierwszy raz słyszą o tej bazie danych.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

MongoDB (dalej – "MongoDB") to rozproszony system, który wspiera poziome skalowanie, to znaczy sharding; a wewnątrz każdego shardu wspiera również redundancję danych, czyli replikację.

Sharding w "MongoDB" (baza danych nierelacyjna) wykonuje automatyczną balansowanie, to znaczy każda kolekcja dokumentów (lub "tabela" w terminach danych relacyjnych) jest dzielona na fragmenty, a serwer automatycznie przenosi je między shardami.

Router zapytań, który rozdziela zapytania, jest dla klienta pewnym klientem, przez którego działa. Już wie, gdzie i jakie dane się znajdują, kierując wszystkie zapytania do właściwego shardu.

Kolejna ważna kwestia: MongoDB to pojedynczy master. Jest jeden Primary – może przyjmować zapisy wspierające te klucze, które ma. Nie można wykonać zapisu w trybie Multi-master.

Wydaliśmy wersję 4.2 – pojawiły się nowe interesujące funkcje. W szczególności wstawiliśmy Lucene – wyszukiwanie – dokładnie wykonalne Java bezpośrednio w "Mongo", co umożliwiło wykonywanie wyszukiwań przez Lucene, podobnie jak w "Elastyku".

Stworzono nowy produkt – Charts, który również jest dostępny w "Atlasie" (własny Cloud "Mongo"). Mają darmowy poziom – można sobie z tym poeksperymentować. Charts bardzo mi się spodobał – wizualizacja danych, bardzo intuicyjna.

Składniki spójności przyczynowej

Policzyłem około 230 artykułów opublikowanych na ten temat – od Lesliera Lamperta. Teraz z pamięci przekażę wam niektóre części tych materiałów.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Wszystko zaczęło się od artykułu Lesleya Lamperta, który został napisany w latach 70. Jak widać, badania w tej dziedzinie wciąż trwają. Teraz Causal consistency zyskuje na zainteresowaniu w związku z rozwojem systemów rozproszonych.

Ograniczenia

Jakie są ograniczenia? To tak naprawdę jeden z najważniejszych punktów, ponieważ ograniczenia, które nakładają systemy produkcyjne, znacznie różnią się od ograniczeń, które występują w artykułach akademickich. Często są one dosyć sztuczne.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

  • Po pierwsze, „MongoDB” to system single master, jak już wspomniałem (co bardzo upraszcza zagadnienie).
  • Uważamy, że system powinien wspierać około 10 tysięcy shardów. Nie możemy podejmować żadnych decyzji architektonicznych, które jawnie ograniczałyby tę wartość.
  • Mamy chmurę, ale zakładamy, że użytkownik powinien mieć możliwość pobrania pliku binarnego, uruchomienia go na swoim laptopie i cieszenia się, że wszystko działa doskonale.
  • Przypuszczamy to, co rzadko się wykorzystuje w badaniach: zewnętrzni klienci mogą robić, co chcą. „MongoDB” jest open source. W związku z tym klienci mogą być na tyle sprytni lub złośliwi – mogą chcieć wszystko zepsuć. Przypuszczamy, że mogą występować bizantyjskie failory.
  • Dla zewnętrznych klientów, którzy znajdują się poza perymetrem – ważne ograniczenie: jeśli ta funkcjonalność jest wyłączona, nie powinno być zauważalnych degradacji wydajności.
  • Jeszcze jeden aspekt – ogólnie antyakademicki: kompatybilność starszych i przyszłych wersji. Stare sterowniki muszą wspierać nowe aktualizacje, a baza danych musi wspierać stare sterowniki.

Ogólnie rzecz biorąc, wszystko to nakłada ograniczenia.

Komponenty Causal consistency

Teraz opowiem o niektórych komponentach. Jeśli rozważyć Causal consistency jako całość, można wyróżnić poszczególne bloki. Wybieraliśmy z prac, które odnoszą się do konkretnego bloku: śledzenie zależności, wybór zegarów, jak te zegary można synchronizować oraz jak zapewniamy bezpieczeństwo – to ogólny plan tego, o czym będę mówić:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Pełne śledzenie zależności (Full Dependency Tracking)

Po co to potrzebne? Aby przy replikacji danych każda rekord, każda zmiana danych zawierała informacje o zmianach, od których zależy. Najprostsza i najbardziej podstawowa zmiana polega na tym, że każda wiadomość, zawierająca rekord, zawiera informacje o poprzednich wiadomościach:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

W tym przykładzie numery w klamrowych nawiasach to numery rekordów. Czasami te rekordy z wartościami są przekazywane w całości, czasami przekazywane są jakieś wersje. Istota polega na tym, że każda zmiana zawiera informacje o poprzednich, co jest dla niej oczywiste.

Dlaczego zdecydowaliśmy się nie korzystać z takiego podejścia (pełne śledzenie)? Oczywiste, ponieważ to podejście jest niepraktyczne: każda zmiana w sieci społecznościowej zależy od wszystkich poprzednich zmian w tej sieci społecznościowej, przesyłając, powiedzmy, „Facebooka” czy „VKontakte” w każdym aktualizacji. Niemniej jednak istnieje wiele badań dotyczących Full Dependency Tracking – w przypadku niektórych sytuacji rzeczywiście działa.

Jawne śledzenie zależności (Explicit Dependency Tracking)

Następne jest bardziej ograniczone. Tutaj również rozpatrywana jest transmisja informacji, ale tylko tych, które są jawnie zależne. Co od czego zależy, zazwyczaj określa już aplikacja. Gdy dane są replikowane, w odpowiedzi wydawane są tylko odpowiedzi, gdy poprzednie zależności zostały spełnione, czyli pokazane. W tym tkwi istota działania spójności przyczynowej.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Widzi, że rekord 5 zależy od rekordów 1, 2, 3, 4 – w związku z tym czeka, zanim klient uzyska dostęp do zmian wprowadzonych decyzją dostępu Penny, gdy wszystkie poprzednie zmiany zostaną już wprowadzone do bazy danych.

To również nam nie odpowiada, ponieważ wciąż jest zbyt wiele informacji, co będzie spowalniało. Istnieje inne podejście...

Zegary Lamporta (Lamport Clock)

Są bardzo stare. Zegar Lamporta oznacza, że te zależności są redukowane do funkcji skalarnej, która nosi nazwę Zegar Lamporta.

Funkcja skalarna to pewna abstrakcyjna liczba. Często nazywana jest czasem logicznym. Przy każdym zdarzeniu ten licznik wzrasta. Licznik, który w tym momencie jest znany procesowi, wysyła każdą wiadomość. Oczywiste jest, że procesy mogą być niesynchronizowane, mogą mieć zupełnie różny czas. Niemniej jednak w taki sposób wymiany wiadomości system w pewien sposób równoważy zegary. Co się dzieje w takim przypadku?

Rozbiłem ten duży shard na dwa, żeby było jasne: Friends mogą żyć w jednym węźle, który zawiera kawałek kolekcji, a Feed – w zupełnie innym węźle, w którym znajduje się kawałek tej kolekcji. Jak mogą się nie znaleźć w kolejności? Na początku Feed powie: „Zreplikowane”, a potem – Friends. Jeśli system nie zapewnia żadnych gwarancji, że Feed nie będzie wyświetlony, dopóki zależności Friends w kolekcji Friends również nie będą dostarczone, to mamy dokładnie sytuację, o której wspomniałem.

Widzicie, jak zwiększa się logiczny czas licznika na Feedzie:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Tak więc główną cechą zegara Lamporta i spójności przyczynowej (wyjaśnionej za pomocą zegara Lamporta) jest to, że jeśli mamy zdarzenia A i B, a zdarzenie B zależy od zdarzenia A*, to wynika z tego, że LogicalTime zdarzenia A jest mniejsze niż LogicalTime zdarzenia B.

* Czasami mówi się również, że A zdarzyło się przed B, czyli A wydarzyło się wcześniej niż B – to rodzaj relacji, która częściowo porządkuje zbiór wszystkich zdarzeń, które w ogóle miały miejsce.

W odwrotną stronę to nie jest prawda. To naprawdę jeden z głównych minusów zegara Lamporta – częściowy porządek. Istnieje pojęcie zdarzeń równoczesnych, to znaczy zdarzeń, w których ani (A zdarzyło się przed B), ani (B zdarzyło się przed A). Przykładem może być równoległe dodawanie kogoś do znajomych przez Leonarda (nawet nie przez Leonarda, a przez Sheldona, na przykład).
To jest właściwość, z której często korzysta się przy pracy z zegarami Lamporta: patrzy się dokładnie na funkcję i na tej podstawie wyciąga się wnioski – może to być, że te zdarzenia są zależne. Ponieważ w jedną stronę to jest prawda: jeśli LogicalTime A jest mniejsze niż LogicalTime B, to B nie może się zdarzyć przed A; a jeśli jest większe, to może.

Zegary wektorowe (Vector Clock)

Logiczną kontynuacją zegara Lamporta są zegary wektorowe. Różnią się tym, że każdy węzeł, który tutaj jest, zawiera swoje własne, oddzielne zegary, które są przesyłane jako wektor.
W tym przypadku widzicie, że zerowy indeks wektora odpowiada za Feed, a pierwszy indeks wektora – za Friends (każdy z tych węzłów). I właśnie teraz będą one się zwiększać: zerowy indeks „Fida” zwiększa się przy zapisie – 1, 2, 3:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Czym różnią się Zegary Wektorowe? Tym, że pozwalają zrozumieć, które zdarzenia występują jednocześnie i kiedy mają miejsce w różnych węzłach. To jest bardzo ważne dla systemu shardingu, jak „MongoDB”. Jednak tego nie wybraliśmy, chociaż to wspaniała rzecz, działa doskonale i prawdopodobnie by nam odpowiadało…

Jeśli mamy 10 tysięcy shardów, nie możemy przesyłać 10 tysięcy komponentów, nawet jeśli je kompresujemy, wymyślamy coś innego – i tak użyteczność będzie wielokrotnie mniejsza niż objętość całego tego wektora. Dlatego, z bólem serca, zrezygnowaliśmy z tego podejścia i przeszliśmy do innego.

Spanner TrueTime. Zegary atomowe

Mówiłem, że będzie mowa o „Spannerze”. To niesamowita rzecz, prosto z XXI wieku: zegary atomowe, synchronizacja GPS.

Jaki jest pomysł? „Spanner” to system stworzony przez Google, który niedawno stał się dostępny dla ludzi (dodali do niego SQL). Każda transakcja ma tam pewien znacznik czasu. Ponieważ czas jest zsynchronizowany*, każdemu zdarzeniu można przypisać określony czas – zegary atomowe mają czas oczekiwania, po którym gwarantowane jest, że „następuje” już inny czas.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

W ten sposób, po prostu zapisując do bazy danych i czekając pewien okres czasu, automatycznie gwarantuje się serializowalność zdarzenia. Mają najsilniejszy model spójności, jaki można w zasadzie sobie wyobrazić – jest to spójność zewnętrzna.

* To główny problem zegarów Lamporta – nigdy nie są zsynchronizowane w systemach rozproszonych. Mogą się rozchodzić, nawet przy obecności NTP wciąż nie działają zbyt dobrze. „Spanner” ma zegary atomowe i synchronizację, wydaje się, że to mikrosekundy.

Dlaczego nie wybraliśmy? Nie zakładamy, że nasi użytkownicy mają wbudowane zegary atomowe. Kiedy się pojawią, wbudowane w każdy laptop, będzie jakaś super fajna synchronizacja GPS – wtedy tak… A na razie najlepsze, co jest możliwe, to „Amazon”, Stacje Bazowe – dla zapaleńców… Dlatego użyliśmy innych zegarów.

Zegary hybrydowe (Hybrid Clock)

To jest właściwie to, co tyka w „MongoDB” podczas zapewniania spójności przyczynowej. W czym są hybrydowe? Hybrydowe to skalarną wartość, ale składa się z dwóch komponentów:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

  • Pierwszy to epoka unixowa (ile sekund minęło od „początku świata komputerowego”).
  • Drugi to pewien inkrement, również 32-bitowy unsigned int.

I to jest wszystko. Istnieje takie podejście: część odpowiedzialna za czas, synchronizuje się cały czas z zegarami; za każdym razem, gdy dochodzi do aktualizacji, ta część synchronizuje się z zegarami i okazuje się, że czas jest zawsze mniej więcej poprawny, a inkrement pozwala rozróżniać zdarzenia, które miały miejsce w tym samym momencie.

Dlaczego to jest ważne dla „MongoDB”? Ponieważ pozwala na wykonywanie pewnych backupów na określony moment czasowy, to znaczy, że zdarzenie jest indeksowane czasem. To jest ważne, gdy potrzebne są pewne zdarzenia; dla bazy danych zdarzenia to zmiany w bazie, które miały miejsce w określonych przedziałach czasu.

Powiem ci jedną, najważniejszą przyczynę (proszę, nikomu tego nie mów)! Zrobiliśmy to w ten sposób, ponieważ tak wyglądają uporządkowane, indeksowane dane w MongoDB OpLog. OpLog to struktura danych, która zawiera wszystkie zmiany w bazie: najpierw trafiają do OpLogu, a potem są stosowane do Storage w przypadku, gdy to jest replikowane lub shardowane.

To była główna przyczyna. Istnieją także praktyczne wymagania dotyczące tworzenia bazy, co oznacza, że ​​musi być prosto - mało kodu, jak najmniej zepsutych rzeczy, które trzeba przepisać i przetestować. To, że nasze oplogi okazały się być indeksowane hybrydowymi zegarami, bardzo pomogło i pozwoliło na dokonanie właściwego wyboru. To naprawdę się opłaciło i jakoś zaczęło działać magicznie w pierwszym prototypie. To było naprawdę niesamowite!

Synchronizacja zegarów

Istnieje kilka sposobów synchronizacji opisanych w literaturze naukowej. Mówię o synchronizacji, gdy mamy dwa różne shard'y. Jeśli mamy jedną replikę, nie ma potrzeby synchronizacji: to jest «single-master»; mamy OpLog, do którego trafiają wszystkie zmiany – w tym przypadku wszystko już jest uporządkowane sekwencyjnie w samym «OpLogu». Ale jeśli mamy dwa różne shard'y, tutaj synchronizacja czasu jest ważna. Tutaj zegary wektorowe byłyby bardziej pomocne! Ale ich nie mamy.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Drugą opcją są «Heartbeat'y». Można wymieniać pewne sygnały, które występują co jednostkę czasu. Ale «Heartbeat'y» są zbyt wolne, nie możemy zapewnić klientowi niskiej latencji.

Prawdziwy czas – to oczywiście wspaniała rzecz. Ale, znów, to prawdopodobnie przyszłość… Chociaż w «Atlasie» można to już zrealizować, istnieją już szybkie synchronizatory czasu od «Amazonu». Ale to nie będzie dostępne dla wszystkich.

Gossiping – to sytuacja, w której wszystkie wiadomości zawierają czas. To mniej więcej to, co stosujemy. Każda wiadomość między węzłami, sterownik, routeri węzłów danych, absolutnie wszystko w «MongoDB» – to jakieś elementy, komponenty baz danych, które zawierają zegary, które płyną. Wszędzie mają wartość hybrydowego czasu, która jest przekazywana. 64 bity? To się sprawdza.

Jak to wszystko działa razem?

Tutaj rozważam jeden zestaw replik, aby było nieco prościej. Mamy Primary i Secondary. Secondary wykonuje replikację i nie zawsze jest w pełni zsynchronizowany z Primary.

Wstawiana jest (insert) do «Primary» z pewną wartością czasu. Ta operacja zwiększa wewnętrzny licznik o 11, jeśli to maksymalne. Lub sprawdza wartości zegarów i synchronizuje się według zegarów, jeśli wartości zegarów są większe. To pozwala na uporządkowanie według czasu.

Po zapisaniu następuje ważny moment. Zegary w «MongoDB» inkrementują się tylko w przypadku zapisu do «OpLogu». To jest wydarzenie, które zmienia stan systemu. W absolutnie wszystkich klasycznych artykułach wydarzeniem uważa się dotarcie wiadomości do węzła: wiadomość dotarła – oznacza, że system zmienił swój stan.

To jest związane z tym, że podczas badania nie do końca można zrozumieć, jak to wiadomość zostanie zinterpretowana. Wiemy na pewno, że jeśli nie jest to odzwierciedlone w „Oplogu”, to nie zostanie to zinterpretowane, a zmianą stanu systemu jest tylko zapis w „Oplogu”. To wszystko upraszcza: model jest uproszczony, pozwala na porządkowanie w ramach jednego zestawu replik oraz wiele innych przydatnych rzeczy.

Zwracana jest wartość, która już została zapisana w „Oplogu” – wiemy, że w „Oplogu” już leży ta wartość, a jej czas to 12. Teraz, powiedzmy, rozpoczyna się odczyt z innego węzła (Secondary) i przekazuje już afterClusterTime w samej wiadomości. Mówi: „Potrzebuję wszystkiego, co wydarzyło się co najmniej po 12 lub w czasie dwunastej” (patrz rys. powyżej).

To, co nazywamy Causal a consistent (CAT). Istnieje takie pojęcie w teorii, że to pewne cięcie czasu, które jest spójne samo w sobie. W tym przypadku można powiedzieć, że to stan systemu, który został zaobserwowany w momencie czasu 12.

Na razie tutaj nic nie ma, ponieważ to jakby symuluje sytuację, w której Secondary musi replikować dane z Primary. Czeka… A oto dane przybyły – zwraca te wartości z powrotem.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Tak to mniej więcej działa. Prawie.

Co znaczy „prawie”? Załóżmy, że jest ktoś, kto przeczytał i zrozumiał, jak to wszystko działa. Zrozumiał, że za każdym razem następuje ClusterTime, aktualizuje wewnętrzne zegary logiczne, a potem następny zapis zwiększa się o jeden. Ta funkcja zajmuje 20 linii. Załóżmy, że ta osoba przesyła maksymalną 64-bitową liczbę, minus jeden.

Dlaczego „minus jeden”? Ponieważ wewnętrzne zegary zostaną wstawione w tę wartość (oczywiście, to największa możliwa wartość i większa niż bieżący czas), potem nastąpi zapis w „Oplogu”, a zegary zostaną zwiększone o jeden – i będzie już maksymalna wartość (tam są po prostu same jedynki, nie ma dokąd iść, unsaint int’y).

Oczywiste jest, że po tym system staje się całkowicie niedostępny dla czegokolwiek. Można go tylko wyładować, oczyścić – mnóstwo ręcznej pracy. Pełna dostępność:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Jeśli to jeszcze gdzieś się replikuje, to cały klaster po prostu zawala. To absolutnie nieakceptowalna sytuacja, którą każdy może zaorganizować bardzo szybko i łatwo! Dlatego traktowaliśmy ten punkt jako jeden z najważniejszych. Jak to zapobiec?

Naszym celem jest podpisywanie clusterTime.

Tak jest przekazywane w wiadomości (do niebieskiego tekstu). Ale zaczęliśmy także generować podpis (niebieski tekst):

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Podpis jest generowany kluczem, który jest przechowywany wewnątrz bazy danych, wewnątrz chronionego obszaru; sam się generuje, aktualizuje (użytkownicy tego nie widzą). Generowany jest hash, a każda wiadomość przy tworzeniu jest podpisywana, a przy odbiorze – walidowana.
Z pewnością pojawia się pytanie u ludzi: „Jak bardzo to wszystko spowalnia?” Mówiłem przecież, że powinno działać szybko, szczególnie przy braku tej funkcji.

Co to oznacza w praktyce korzystanie z Causal consistency? To pokazanie parametru afterClusterTime. A bez niego po prostu będzie przekazywał wartości w każdych okolicznościach. Gossiping, zaczynając od wersji 3.6, działa zawsze.

Jeśli pozostawimy stałe generowanie podpisów, spowolni to system nawet przy braku funkcji, co nie odpowiada naszym podejściom i wymaganiom. Co zrobiliśmy?

Zrób to szybko!

Zdecydowanie prosta sprawa, ale ciekawy trik – podzielę się, może ktoś to zainteresuje.
Mamy hash, w którym przechowywane są podpisane dane. Wszystkie dane przechodzą przez cache. Cache podpisuje nie konkretny czas, a Range. Kiedy przychodzi jakieś wartość, generujemy Range, maskujemy ostatnie 16 bitów i to wartość podpisujemy:

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Otrzymując taki podpis, przyspieszamy system (warunkowo) 65 tysięcy razy. Działa to świetnie: gdy przeprowadzono eksperymenty – czas na sekwencyjną aktualizację skrócił się rzeczywiście 10 tysięcy razy. Oczywiście, że gdy są w nieładzie, to się nie udaje. Ale w większości praktycznych przypadków to działa. Kombinacja podpisu Range razem z podpisem pozwoliła rozwiązać problem bezpieczeństwa.

Jasne jest, że każdy serwis stara się unikać przestojów. W naszym przypadku wierzymy, że ostatnie awarie pomogły uczynić quay.io lepszym. Wyciągnęliśmy kilka głównych lekcji, którymi chcemy się podzielić:

Lekcje, które z tego wyciągnęliśmy:

  • Trzeba czytać materiały, historie, artykuły, bo mamy wiele interesujących rzeczy do omówienia. Kiedy pracujemy nad jakąś funkcjonalnością (szczególnie teraz, gdy realizowaliśmy transakcje itp.), trzeba czytać i zrozumieć. To zajmuje czas, ale jest bardzo pomocne, bo staje się jasne, gdzie się znajdujemy. W rzeczywistości nic nowego nie wymyśliliśmy – po prostu wykorzystaliśmy składniki.

    W rzeczywistości istnieje pewna różnica w myśleniu, gdy ma miejsce konferencja akademicka (np. „Sigmon”) – tam wszyscy skupiają się na nowych ideach. Jaka jest nowość naszego algorytmu? Tutaj nowości nie ma. Nowość polega raczej na tym, jak połączyliśmy istniejące podejścia. Dlatego najpierw trzeba czytać klasyków, zaczynając od Lamporta.

  • W produkcji zupełnie inne są wymagania. Jestem pewien, że wielu z was stwierdza, że nie ma do czynienia z „sferycznymi” bazami danych w abstrakcyjnej próżni, a z normalnymi, rzeczywistymi rzeczami, które mają problemy z dostępnością, opóźnieniami i odpornością na awarie.
  • Ostatnie to to, że musieliśmy rozważyć różne pomysły i połączyć kilka całkowicie różnych artykułów w jeden podejście. Pomysł dotyczący podpisywania, na przykład, pochodził z artykułu, który omawiał protokół Paxos, który dotyczył niewizjańskich awarii w ramach protokołu autoryzacji, a w przypadku wizjańskich – poza protokołem autoryzacji… Generalnie, to dokładnie to, co w końcu zrobiliśmy.

    Nie ma tu absolutnie nic nowego! Ale gdy tylko wszystko to połączyliśmy… To tak, jakby powiedzieć, że przepis na sałatkę Olivier jest niczym, bo jajka, majonez i ogórki już wymyślono… To w zasadzie ta sama historia.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Na tym zakończę. Dziękuję!

Pytania

Pytanie z sali (dalej – P): – Dziękuję, Michale, za prezentację! Temat czasu jest interesujący. Używasz Gossiping. Powiedziałeś, że każdy ma swój czas, każdy zna swój lokalny czas. Zrozumiałem, że mamy kierowcę – klientów z kierowcami może być wielu, także planistów zapytań, shardów też wiele… A do czego doprowadzi system, jeśli nagle wystąpi różnica: ktoś uzna, że jest minutę do przodu, ktoś inny – minutę w tył? Gdzie się znajdziemy?

MT: – To bardzo dobre pytanie! Właśnie chciałem powiedzieć o shardach. Jeśli dobrze rozumiem to pytanie, mamy sytuację: jest shard 1 i shard 2, a odczyty odbywają się z tych dwóch shardów – mają one rozbieżność, nie współdziałają ze sobą, ponieważ czas, który znają – jest różny, szczególnie czas, który mają w oplogach.
Załóżmy, że shard 1 zrobił milion zapisów, a shard 2 – w ogóle nic, podczas gdy zapytanie przyszło do obu shardów. I shard pierwszy ma afterClusterTime większy niż milion. W takiej sytuacji, jak wyjaśniłem, shard 2 w ogóle nigdy nie odpowie.

P: – Chciałem zapytać, jak one się synchronizują i wybierają jeden logiczny czas?

MT: – Synchronizują się bardzo prosto. Shard, kiedy otrzymuje afterClusterTime i nie znajduje czasu w 'Oplogu' – inicjuje no approved. To znaczy, że ręcznie podnosi swój czas do tej wartości. Oznacza to, że nie ma zdarzeń odpowiadających temu zapytaniu. Tworzy to zdarzenie sztucznie i w ten sposób staje się Causal Consistent.

P: – A jeśli po tym przyjdą jakieś zdarzenia, które gdzieś w sieci zniknęły?

MT: – Shard jest zaprojektowany tak, że one już nie przyjdą, ponieważ to jest single master. Jeśli już zapisał, to one już nie wrócą, a będą później. Nie może się zdarzyć, że gdzieś coś utknęło, potem on zrobi no write, a potem te zdarzenia przychodzą – i Causal consistency zostaje naruszona. Kiedy on robi no write, wszystko musi przyjść dalej (poczeka na nie).

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

P: – Mam kilka pytań dotyczących kolejek. Causal consistency zakłada, że istnieje pewna kolejność działań do wykonania. Co się stanie, jeśli jeden pakiet zaginie? Oto poszedł 10-ty, 11… 12-ty zniknął, a wszystkie pozostałe czekają, aż zostanie wykonany. A nagle nasza maszyna umarła, nie możemy nic zrobić. Czy istnieje maksymalna długość kolejki, która się gromadzi, zanim zostanie wykonana? Jaki błąd krytyczny występuje przy utracie jakiegoś jednego stanu? Zwłaszcza jeśli zapisujemy, że istnieje jakieś wcześniejsze zdarzenie, to od niego powinniśmy się jakoś odnieść? A od niego się nie odnieśliśmy!

MT: – Również wspaniałe pytanie! Co robimy? W MongoDB istnieje pojęcie quorum writes, quorum reads. W jakich przypadkach wiadomość może zniknąć? Gdy zapis jest niekworumowy lub gdy odczyt jest niekworumowy (może przyjść jakiś śmieć).
W odniesieniu do spójności przypadkowej przeprowadziliśmy dużą eksperymentalną weryfikację, której wynikiem jest to, że w przypadku, gdy zapisy i odczyty są niekworumowe, mogą występować naruszenia spójności przypadkowej. Dokładnie to, co mówisz!

Nasza rada: używaj chociaż kworumowego odczytu przy użyciu spójności przypadkowej. W tym przypadku nic nie zaginie, nawet jeśli kworumowa zapis zniknie… To jest ortogonalna sytuacja: jeśli użytkownik nie chce, aby dane zniknęły, musi korzystać z kworumowej zapisu. Spójność przypadkowa nie daje gwarancji trwałości. Gwarancję trwałości daje replikacja i powiązania związane z replikacją.

P: – Kiedy tworzymy instancję, która wykonuje sharding (nie master, a slave odpowiednio), opiera się ona na czasach unixowych własnej maszyny lub na czasie „mastera”; synchonizuje się po raz pierwszy lub regularnie?

MT: – Teraz wyjaśnię. Shard (tj. pozioma partycja) zawsze ma Primary. W shardzie może być „master” i mogą być repliki. Ale shard zawsze wspiera zapis, ponieważ musi wspierać pewną domenę (w shardzie znajduje się Primary).

P: – Czyli wszystko zależy wyłącznie od „mastera”? Zawsze używane jest „master”-czas?

MT: – Tak. Można metaforycznie powiedzieć: zegary tykają, gdy zachodzi zapis w „masterze”, w „OpLog”.

P: – Mamy klienta, który się łączy i nie musi nic wiedzieć o czasie?

MT: – W ogóle nic nie musi wiedzieć! Jeśli mówimy o tym, jak to działa po stronie klienta: klient, gdy chce korzystać z spójności przypadkowej, musi otworzyć sesję. Teraz wszystko tam jest: i transakcje w sesji, i odbieranie praw… Sesja to uporządkowanie logicznych zdarzeń, które zachodzą z klientem.

Jeśli otwiera tę sesję i mówi, że chce spójności przypadkowej (jeśli domyślnie sesja wspiera spójność przypadkową), wszystko automatycznie działa. Sterownik zapamiętuje ten czas i zwiększa go, gdy otrzymuje nowe wiadomości. Zapamiętuje, jaka odpowiedź wróciła z serwera, który zwrócił dane. Następne zapytanie będzie zawierało afterCluster (czas większy niż ten).

Klient nie musi znać absolutnie nic! Jest to całkowicie nieprzezroczyste dla niego. Jeśli ludzie korzystają z tych funkcji, co to umożliwia? Po pierwsze, można bezpiecznie odczytywać dane z sekundarnych baz danych: można zapisywać w Primary, a czytać z geograficznie replikowanych sekundarnych i być pewnym, że to działa. Przy tym sesje zapisane w Primary można przekazać nawet na Secondary, tzn. można używać nie jednej sesji, a kilku.

P: – Z tematem spójności ostatecznej mocno związany jest nowy obszar w informatyce – typy danych CRDT (Conflict-free Replicated Data Types). Czy rozważaliście integrację tych typów danych w bazę i co o tym sądzicie?

MT: – Dobre pytanie! CRDT ma sens przy konfliktach podczas zapisu: w MongoDB – pojedynczy master.

P: – Mam pytanie od devopsów. W dzisiejszym świecie występują takie iezuiterskie sytuacje, kiedy zdarza się bizantyński błąd, a źli ludzie w obrębie chronionego obszaru zaczynają grzebać w protokole, wysyłając specjalnie skonstruowane pakiety?

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

MT: – Źli ludzie w obrębie obszaru – to jak koń trojański! Źli ludzie w obrębie obszaru mogą zrobić wiele złych rzeczy.

P: – Oczywiście, że zostawianie w serwerze, powiedzmy, dziury, przez którą można wsunąć zoo słoni i obalić cały klaster na zawsze… To zajmie czas, aby ręcznie to naprawić… To, delikatnie mówiąc, niewłaściwe. Z drugiej strony, interesujące jest to: w rzeczywistym życiu, w praktyce pojawiają się takie sytuacje, kiedy takie wewnętrzne ataki rzeczywiście występują?

MT: – Ponieważ rzadko spotykam się z naruszeniami bezpieczeństwa w prawdziwym życiu, nie mogę powiedzieć – może one się zdarzają. Ale jeśli chodzi o filozofię deweloperską, to my uważamy tak: mamy obszar, który zapewnia ludziom zajmującym się bezpieczeństwem – to zamek, mur; a wewnątrz obszaru można robić wszystko, co się chce. Oczywiście, że są użytkownicy, którzy mogą tylko oglądać, a są też tacy, którzy mogą usunąć katalog.

W zależności od uprawnień, szkody, które użytkownicy mogą wyrządzić, mogą być myszą lub mogą być także słoniem. Oczywiście, że użytkownik z pełnymi prawami może zrobić absolutnie wszystko. Użytkownik z ograniczonymi prawami może wyrządzić znacznie mniej szkód. W szczególności nie może zniszczyć systemu.

P: – W zabezpieczonym obszarze ktoś może zacząć tworzyć nieoczekiwane protokoły dla serwera, by przyprawić serwer o ból głowy, a jeśli będzie szczęście, to także cały klaster... Czy może być aż tak "dobrze"?

MT: – Nigdy nie słyszałem o takich rzeczach. To, że w ten sposób można zablokować serwer, nie jest tajemnicą. Zablokować wewnątrz, będąc na protokole, jako autoryzowany użytkownik, który może wpisać coś w wiadomości... Właściwie nie można, ponieważ i tak będzie weryfikowane. Istnieje możliwość wyłączenia tej autoryzacji dla użytkowników, którzy jej nie chcą — to wtedy ich problem; mówiąc wprost, sami zniszczyli mury i można wcisnąć tam słonia, który je zadepcze... Ogólnie rzecz biorąc, można się przebrać za serwisanta, przyjść i wyciągnąć!

P: – Dziękuję za prezentację. Sergey („Yandex”). W „Mongo” jest stała, która limituje liczbę głosujących członków w Replica Set, i ta stała wynosi 7 (siedem). Dlaczego to jest stała? Dlaczego to nie jest jakiś parametr?

MT: – Replica Set może mieć i 40 węzłów. Tam zawsze jest większość. Nie wiem, która to wersja...

P: – W Replica Set można uruchamiać także niegłosujących członków, ale głosujących — maksymalnie 7. Jak w takim przypadku poradzić sobie z wyłączeniem, jeśli nasz Replica Set jest rozciągnięty na 3 centra danych? Jedno centrum danych może się swobodnie wyłączyć, a jeszcze jedna maszyna może wypaść.

MT: – To już trochę wychodzi poza zakres prezentacji. To ogólne pytanie. Może później mogę o tym opowiedzieć.

HighLoad++, Michaił Tjuleniew (MongoDB): Spójność przyczynowa: od teorii do praktyki

Odtwarzaj wideo

Trochę reklamy 🙂

Dziękujemy, że jesteś z nami. Podobają Ci się nasze artykuły? Chcesz zobaczyć więcej interesujących materiałów? Wspieraj nas składając zamówienie lub polecając nas znajomym, chmurowe VPS dla programistów od 4,99 $, unikatowy odpowiednik serwerów entry-level, który został stworzony przez nas dla Ciebie: Cała prawda o VPS (KVM) E5-2697 v3 (6 rdzeni) 10GB DDR4 480GB SSD 1Gbps od 19 $ lub jak prawidłowo podzielić serwer? (dostępne opcje z RAID1 i RAID10, do 24 rdzeni i do 40GB DDR4).

Dell R730xd dwa razy tańszy w centrum danych Equinix Tier IV w Amsterdamie? Tylko u nas 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100TB od 199 dolarów w Holandii! Dell R420 — 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB — od 99 dolarów! Czytaj o tym Jak zbudować infrastrukturę klasy korporacyjnej z zastosowaniem serwerów Dell R730xd E5-2650 v4 kosztujących 9000 euro za grosze?

Ź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