Kiedy używać bazy danych opartej na CRDT

Roshan Kumar jest starszym menedżerem produktu w Redis Labs.

Naginanie spójności i dostępności, jak opisano w twierdzeniu CAP, było wielkim wyzwaniem dla architektów aplikacji rozproszonych geograficznie. Partycja sieciowa jest nieunikniona. Duże opóźnienia między centrami danych zawsze powodują pewne rozłączenia między centrami danych na krótki okres czasu. W związku z tym tradycyjne architektury dla aplikacji rozproszonych geograficznie są zaprojektowane tak, aby albo zrezygnować ze spójności danych, albo uderzyć w dostępność.

Niestety nie można pozwolić sobie na poświęcenie dostępności dla interaktywnych aplikacji użytkownika. W ostatnim czasie architekci postarali się o konsekwencję i przyjęli model ostatecznej spójności. W tym modelu aplikacje są zależne od systemu zarządzania bazą danych, aby scalić wszystkie lokalne kopie danych, aby ostatecznie były spójne.

Wszystko wygląda dobrze z ostatecznym modelem spójności, dopóki nie wystąpią konflikty danych. Kilka modeli ostatecznej spójności obiecuje najlepsze wysiłki w celu rozwiązania konfliktów, ale nie gwarantuje silnej spójności. Dobra wiadomość jest taka, że ​​modele zbudowane wokół bezkonfliktowych replikowanych typów danych (CRDT) zapewniają silną ostateczną spójność.

CRDT osiągają silną ostateczną spójność poprzez z góry określony zestaw reguł rozwiązywania konfliktów i semantyki. Aplikacje zbudowane w oparciu o bazy danych oparte na CRDT muszą być zaprojektowane tak, aby uwzględniać semantykę rozwiązywania konfliktów. W tym artykule przyjrzymy się, jak projektować, rozwijać i testować aplikacje rozproszone geograficznie przy użyciu bazy danych opartej na CRDT. Przeanalizujemy również cztery przykładowe przypadki użycia: liczniki, rozproszone buforowanie, wspólne sesje i pozyskiwanie danych z wielu regionów.

Mój pracodawca, Redis Labs, niedawno ogłosił obsługę CRDT w Redis Enterprise, z bezkonfliktowymi replikowanymi typami danych, które dołączają do bogatego portfolio struktur danych - ciągów znaków, skrótów, list, zestawów, sortowanych zestawów, pól bitowych, geograficznych, hiperlogów i strumieni - w nasz produkt bazodanowy. Jednak poniższa dyskusja dotyczy nie tylko Redis Enterprise, ale wszystkich baz danych opartych na CRDT.

Bazy danych dla aplikacji rozproszonych geograficznie

W przypadku aplikacji rozproszonych geograficznie powszechne jest uruchamianie usług lokalnie dla klientów. Zmniejsza to ruch sieciowy i opóźnienia spowodowane przez obie strony. W wielu przypadkach architekci projektują usługi w celu połączenia z lokalną bazą danych. Następnie pojawia się pytanie, w jaki sposób utrzymujesz spójne dane we wszystkich bazach danych. Jedną z opcji jest rozwiązanie tego problemu na poziomie aplikacji - możesz napisać okresowy proces zadania, który będzie synchronizował wszystkie bazy danych. Lub możesz polegać na bazie danych, która będzie synchronizować dane między bazami danych.

W dalszej części artykułu zakładamy, że wybierzesz drugą opcję - pozwól bazie danych wykonać pracę. Jak pokazano na rysunku 1 poniżej, aplikacja rozproszona geograficznie uruchamia usługi w wielu regionach, przy czym każda usługa łączy się z lokalną bazą danych. Podstawowy system zarządzania bazą danych synchronizuje dane między bazami danych wdrożonymi w regionach.

Redis Labs

Modele spójności danych

Model spójności to kontrakt między rozproszoną bazą danych a aplikacją, który określa, jak czyste są dane między operacjami zapisu i odczytu.

Na przykład w modelu silnej spójności baza danych gwarantuje, że aplikacje zawsze odczytują ostatni zapis. Dzięki spójności sekwencyjnej baza danych zapewnia, że ​​kolejność odczytywanych danych jest zgodna z kolejnością, w jakiej zostały zapisane w bazie danych. W modelu ostatecznej spójności rozproszona baza danych obiecuje synchronizację i konsolidację danych między replikami baz danych w tle. Dlatego, jeśli zapiszesz dane w jednej replice bazy danych i odczytasz je z innej, możliwe, że nie przeczytasz najnowszej kopii danych.

Mocna konsystencja

Zatwierdzanie dwufazowe jest powszechną techniką uzyskiwania silnej spójności. Tutaj, dla każdej operacji zapisu (dodawania, aktualizowania, usuwania) w lokalnym węźle bazy danych, węzeł bazy danych propaguje zmiany do wszystkich węzłów bazy danych i czeka na potwierdzenie przez wszystkie węzły. Następnie węzeł lokalny wysyła zatwierdzenie do wszystkich węzłów i czeka na kolejne potwierdzenie. Aplikacja będzie mogła odczytać dane dopiero po drugim zatwierdzeniu. Rozproszona baza danych nie będzie dostępna do operacji zapisu, gdy sieć zostanie rozłączona między bazami danych.

Ostateczna spójność

Główną zaletą modelu ostatecznej spójności jest to, że baza danych będzie dostępna do wykonywania operacji zapisu nawet w przypadku awarii połączenia sieciowego między rozproszonymi replikami baz danych. Ogólnie rzecz biorąc, ten model pozwala uniknąć czasu obiegu w obie strony związanego z zatwierdzeniem dwufazowym, a zatem obsługuje znacznie więcej operacji zapisu na sekundę niż inne modele. Jednym z problemów, który musi rozwiązać ostateczna spójność, są konflikty - jednoczesne zapisy na tym samym elemencie w dwóch różnych lokalizacjach. W zależności od tego, w jaki sposób unikają lub rozwiązują konflikty, ostatecznie spójne bazy danych są dalej klasyfikowane w następujących kategoriach:

  1. Ostatni pisarz wygrywa (LWW).  W tej strategii rozproszone bazy danych opierają się na synchronizacji znaczników czasu między serwerami. Bazy danych wymieniają znacznik czasu każdej operacji zapisu wraz z samymi danymi. W przypadku konfliktu wygrywa operacja zapisu z najnowszym znacznikiem czasu.

    Wadą tej techniki jest to, że zakłada, że ​​wszystkie zegary systemu są zsynchronizowane. W praktyce synchronizacja wszystkich zegarów systemu jest trudna i kosztowna.

  2. Spójność ostateczna oparta na kworum: ta technika jest podobna do zatwierdzania dwufazowego. Jednak lokalna baza danych nie czeka na potwierdzenie ze wszystkich baz danych; po prostu czeka na potwierdzenie z większości baz danych. Potwierdzenie przez większość ustanawia kworum. W przypadku konfliktu wygrywa operacja zapisu, która ustanowiła kworum.

    Z drugiej strony ta technika dodaje opóźnienie sieciowe do operacji zapisu, co sprawia, że ​​aplikacja jest mniej skalowalna. Ponadto lokalna baza danych nie będzie dostępna do zapisu, jeśli zostanie odizolowana od innych replik bazy danych w topologii.

  3. Replikacja scalająca : w tym tradycyjnym podejściu, które jest powszechne w relacyjnych bazach danych, scentralizowany agent scalający łączy wszystkie dane. Ta metoda zapewnia również pewną elastyczność we wdrażaniu własnych reguł rozwiązywania konfliktów.

    Replikacja scalająca jest zbyt wolna, aby obsługiwać angażujące aplikacje w czasie rzeczywistym. Ma również pojedynczy punkt awarii. Ponieważ ta metoda nie obsługuje wstępnie ustalonych reguł rozwiązywania konfliktów, często prowadzi do błędnych implementacji rozwiązywania konfliktów.

  4. Replikowany typ danych bez konfliktów (CRDT): szczegółowe informacje na temat CRDT znajdziesz w kilku następnych sekcjach. Krótko mówiąc, bazy danych oparte na CRDT obsługują typy danych i operacje, które zapewniają spójność ostateczną bez konfliktów. Bazy danych oparte na CRDT są dostępne nawet wtedy, gdy repliki rozproszonych baz danych nie mogą wymieniać danych. Zawsze zapewniają lokalne opóźnienie operacji odczytu i zapisu.

    Ograniczenia? Nie wszystkie przypadki użycia baz danych odnoszą korzyści z CRDT. Ponadto semantyka rozwiązywania konfliktów dla baz danych opartych na CRDT jest wstępnie zdefiniowana i nie można jej zastąpić.

Co to są CRDT?

CRDT to specjalne typy danych, które zbiegają dane ze wszystkich replik baz danych. Popularne CRDT to liczniki G (liczniki tylko do wzrostu), liczniki PN (liczniki dodatnio-ujemne), rejestry, zestawy G (zestawy tylko do wzrostu), zestawy 2P (zestawy dwufazowe), zestawy OR ( zbiory obserwowane-usuń), itp. Za kulisami opierają się one na następujących właściwościach matematycznych w celu zbieżności danych:

  1. Własność przemienna: a ☆ b = b ☆ a
  2. Własność asocjacyjna: a ☆ (b ☆ c) = (a ☆ b) ☆ c
  3. Idempotencja:  a ☆ a = a

Licznik G jest doskonałym przykładem operacyjnego CRDT, który łączy operacje. Tutaj a + b = b + a i a + (b + c) = (a + b) + c. Repliki wymieniają między sobą tylko aktualizacje (dodatki). CRDT połączy aktualizacje, dodając je. Na przykład zestaw G stosuje idempotencję ({a, b, c} U {c} = {a, b, c}) do scalenia wszystkich elementów. Idempotence unika powielania elementów dodanych do struktury danych, gdy podróżują i zbiegają się różnymi ścieżkami.

Typy danych CRDT i ich semantyka rozwiązywania konfliktów

Struktury danych wolne od konfliktów: liczniki G, liczniki PN, zestawy G

Wszystkie te struktury danych są zaprojektowane bez konfliktów. Poniższe tabele pokazują, w jaki sposób dane są synchronizowane między replikami baz danych.

Redis Labs Redis Labs

Liczniki G i liczniki PN są popularne w przypadkach użycia, takich jak sondowanie globalne, liczniki strumieni, śledzenie aktywności i tak dalej. Zestawy G są intensywnie wykorzystywane do wdrażania technologii blockchain. Na przykład bitcoiny wykorzystują wpisy łańcucha bloków zawierające tylko dopisywanie.

Rejestry: ciągi, skróty

Rejestry z natury nie są wolne od konfliktów. Zwykle postępują zgodnie z zasadami rozwiązywania konfliktów w oparciu o LWW lub kworum. Rysunek 4 pokazuje przykład, w jaki sposób rejestr rozwiązuje konflikt, postępując zgodnie z polityką LWW.

Redis Labs

Rejestry są używane głównie do przechowywania pamięci podręcznej i danych sesji, informacji o profilu użytkownika, katalogu produktów itp.

Zestawy 2P

Zestawy dwufazowe zawierają dwa zestawy G - jeden dla elementów dodanych, a drugi dla elementów usuniętych. Repliki wymieniają dodatki G-set podczas synchronizacji. Konflikt pojawia się, gdy ten sam element zostanie znaleziony w obu zestawach. W niektórych bazach danych opartych na CRDT, takich jak Redis Enterprise, jest to obsługiwane przez zasadę „Dodaj wygrywa po usunięciu”.

Redis Labs

Zestaw 2P to dobra struktura danych do przechowywania wspólnych danych sesji, takich jak koszyki, udostępniony dokument lub arkusz kalkulacyjny.

Jak zaprojektować aplikację do korzystania z bazy danych opartej na CRDT

Łączenie aplikacji z bazą danych opartą na CRDT nie różni się od łączenia aplikacji z jakąkolwiek inną bazą danych. Jednak ze względu na ostateczne zasady spójności aplikacja musi przestrzegać określonego zestawu reguł, aby zapewnić spójne środowisko użytkownika. Trzy klucze: 

  1. Spraw, aby Twoja aplikacja była bezstanowa. Aplikacja bezstanowa jest zwykle oparta na interfejsie API. Każde wywołanie API powoduje odtworzenie całej wiadomości od podstaw. Gwarantuje to, że zawsze możesz pobrać czystą kopię danych w dowolnym momencie. Niskie lokalne opóźnienie oferowane przez bazę danych opartą na CRDT sprawia, że ​​rekonstrukcja wiadomości jest szybsza i łatwiejsza. 

  2. Wybierz odpowiedni CRDT, który pasuje do Twojego przypadku użycia. Licznik jest najprostszym z CRDT. Może być stosowany w przypadkach użycia, takich jak głosowanie globalne, śledzenie aktywnych sesji, pomiary itp. Jeśli jednak chcesz scalić stan rozproszonych obiektów, musisz wziąć pod uwagę również inne struktury danych. Na przykład w przypadku aplikacji, która umożliwia użytkownikom edycję udostępnionego dokumentu, możesz chcieć zachować nie tylko zmiany, ale także kolejność, w jakiej zostały wykonane. W takim przypadku zapisanie zmian na liście opartej na CRDT lub w strukturze danych kolejki byłoby lepszym rozwiązaniem niż przechowywanie ich w rejestrze. Ważne jest również, aby rozumieć semantykę rozwiązywania konfliktów wymuszaną przez CRDT i aby Twoje rozwiązanie było zgodne z regułami.
  3. CRDT nie jest rozwiązaniem uniwersalnym. Chociaż CRDT jest rzeczywiście doskonałym narzędziem w wielu przypadkach użycia, może nie być najlepszym narzędziem we wszystkich przypadkach użycia (na przykład transakcje ACID). Bazy danych oparte na CRDT ogólnie dobrze pasują do architektury mikrousług, w której masz dedykowaną bazę danych dla każdej mikrousługi.

Głównym wnioskiem jest to, że aplikacja powinna skupić się na logice i delegować zarządzanie danymi i złożoność synchronizacji do bazowej bazy danych.

Testowanie aplikacji z rozproszoną bazą danych typu multi-master

Aby uzyskać szybsze wejście na rynek, zalecamy spójną konfigurację programowania, testowania, przemieszczania i produkcji. Oznacza to między innymi, że konfiguracja programistyczna i testowa musi mieć zminiaturyzowany model rozproszonej bazy danych. Sprawdź, czy baza danych oparta na CRDT jest dostępna jako kontener Docker lub urządzenie wirtualne. Wdróż repliki bazy danych w różnych podsieciach, aby można było symulować konfigurację klastra połączonego i odłączonego.

Testowanie aplikacji z rozproszoną bazą danych typu multi-master może wydawać się skomplikowane. Ale przez większość czasu będziesz testować tylko spójność danych i dostępność aplikacji w dwóch sytuacjach: gdy rozproszone bazy danych są połączone i gdy między bazami danych istnieje partycja sieciowa.

Konfigurując trójwęzłową rozproszoną bazę danych w środowisku programistycznym, można objąć (a nawet zautomatyzować) większość scenariuszy testowych w testowaniu jednostkowym. Oto podstawowe wskazówki dotyczące testowania aplikacji: