Dlaczego możesz potrzebować replikacji półsynchronicznej?

Cześć wszystkim. Vladislav Rodin jest w kontakcie. Obecnie prowadzę kursy z zakresu architektury oprogramowania i architektury oprogramowania o wysokim obciążeniu w OTUS. W oczekiwaniu na rozpoczęcie nowego przepływu kursu „Architekt wysokiego obciążenia” Postanowiłem napisać krótki fragment autorskiego materiału, którym chcę się z Wami podzielić.

Dlaczego możesz potrzebować replikacji półsynchronicznej?

Wprowadzenie

Ze względu na to, że dysk twardy może wykonać jedynie około 400-700 operacji na sekundę (co jest nieporównywalne z typowymi operacjami rps w systemie o dużym obciążeniu), klasyczna dyskowa baza danych stanowi wąskie gardło tej architektury. Dlatego należy zwrócić szczególną uwagę na wzorce skalowania tego magazynu.

Obecnie istnieją 2 wzorce skalowania bazy danych: replikacja i fragmentowanie. Sharding umożliwia skalowanie operacji zapisu, a co za tym idzie, zmniejszenie liczby obrotów na sekundę na zapis na serwer w klastrze. Replikacja umożliwia wykonanie tego samego, ale z operacjami odczytu. Właśnie temu wzorowi poświęcony jest ten artykuł.

Replikacja

Jeśli spojrzeć na replikację na bardzo wysokim poziomie, sprawa jest prosta: miałeś jeden serwer, były na nim dane, a potem ten serwer nie był już w stanie udźwignąć obciążenia odczytem tych danych. Dodajesz jeszcze kilka serwerów, synchronizujesz dane na wszystkich serwerach, a użytkownik może czytać dane z dowolnego serwera w klastrze.

Pomimo pozornej prostoty istnieje kilka opcji klasyfikacji różnych implementacji tego schematu:

  • Według ról w klastrze (master-master lub master-slave)
  • Według wysłanych obiektów (na podstawie wierszy, na podstawie instrukcji lub mieszanych)
  • Zgodnie z mechanizmem synchronizacji węzła

Dziś zajmiemy się punktem 3.

Jak następuje zatwierdzenie transakcji?

Temat ten nie jest bezpośrednio związany z replikacją, można na ten temat napisać osobny artykuł, ale ponieważ dalsza lektura bez zrozumienia mechanizmu zatwierdzania transakcji nie ma sensu, przypomnę Wam najbardziej podstawowe rzeczy. Zatwierdzenie transakcji odbywa się w 3 etapach:

  1. Rejestrowanie transakcji do dziennika bazy danych.
  2. Korzystanie z transakcji w silniku bazy danych.
  3. Zwrotne potwierdzenie do klienta, że ​​transakcja została pomyślnie zrealizowana.

W różnych bazach danych ten algorytm może mieć niuanse: na przykład w silniku InnoDB bazy danych MySQL znajdują się 2 logi: jeden do replikacji (dziennik binarny), a drugi do obsługi ACID (dziennik cofania/ponawiania), natomiast w PostgreSQL istnieje jeden dziennik, który wykonuje obie funkcje (dziennik zapisu z wyprzedzeniem = WAL). Ale to, co przedstawiono powyżej, jest właśnie ogólną koncepcją, która pozwala nie brać pod uwagę takich niuansów.

Replikacja synchroniczna (synchroniczna).

Dodajmy logikę replikującą otrzymane zmiany do algorytmu zatwierdzania transakcji:

  1. Rejestrowanie transakcji do dziennika bazy danych.
  2. Korzystanie z transakcji w silniku bazy danych.
  3. Wysyłanie danych do wszystkich replik.
  4. Otrzymanie potwierdzenia ze wszystkich replik, że transakcja została na nich zakończona.
  5. Zwrotne potwierdzenie do klienta, że ​​transakcja została pomyślnie zrealizowana.

Dzięki takiemu podejściu uzyskujemy szereg wad:

  • klient czeka, aż zmiany zostaną zastosowane do wszystkich replik.
  • wraz ze wzrostem liczby węzłów w klastrze zmniejszamy prawdopodobieństwo, że operacja zapisu zakończy się sukcesem.

Jeśli w punkcie 1. wszystko jest mniej więcej jasne, to warto wyjaśnić przyczyny punktu 2. Jeśli podczas replikacji synchronicznej nie otrzymamy odpowiedzi od chociaż jednego węzła, wycofujemy transakcję. Zatem zwiększając liczbę węzłów w klastrze, zwiększasz prawdopodobieństwo niepowodzenia operacji zapisu.

Czy możemy poczekać na potwierdzenie tylko od określonego odsetka węzłów, np. od 51% (kworum)? Tak, ale w wersji klasycznej wymagane jest potwierdzenie ze wszystkich węzłów, gdyż w ten sposób możemy zapewnić pełną spójność danych w klastrze, co jest niewątpliwą zaletą tego typu replikacji.

Replikacja asynchroniczna (asynchroniczna).

Zmodyfikujmy poprzedni algorytm. Dane do replik wyślemy „kiedyś później”, a „kiedyś później” zmiany zostaną zastosowane do replik:

  1. Rejestrowanie transakcji do dziennika bazy danych.
  2. Korzystanie z transakcji w silniku bazy danych.
  3. Zwrotne potwierdzenie do klienta, że ​​transakcja została pomyślnie zrealizowana.
  4. Wysyłanie danych do replik i wprowadzanie w nich zmian.

Takie podejście powoduje, że klaster działa szybko, gdyż nie każemy klientowi czekać, aż dane dotrą do replik i nawet zostaną zatwierdzone.

Jednak warunek zrzutu danych na repliki „jakiś czas później” może prowadzić do utraty transakcji i do tego utraty transakcji potwierdzonej przez użytkownika, bo gdyby dane nie zdążyły zostać zreplikowane, potwierdzenie dla klienta o powodzeniu operacji został wysłany, a węzeł, do którego dotarły zmiany, spowodował awarię dysku twardego, tracimy transakcję, co może prowadzić do bardzo przykrych konsekwencji.

Replikacja półsynchroniczna

Wreszcie dochodzimy do replikacji półsynchronicznej. Ten typ replikacji nie jest zbyt dobrze znany ani zbyt powszechny, ale cieszy się dużym zainteresowaniem, ponieważ może łączyć zalety replikacji synchronicznej i asynchronicznej.

Spróbujmy połączyć 2 poprzednie podejścia. Nie zatrzymamy klienta na długo, ale będziemy wymagać replikacji danych:

  1. Rejestrowanie transakcji do dziennika bazy danych.
  2. Korzystanie z transakcji w silniku bazy danych.
  3. Wysyłanie danych do replik.
  4. Otrzymanie potwierdzenia z repliki, że zmiany zostały otrzymane (zostaną zastosowane „kiedyś później”).
  5. Zwrotne potwierdzenie do klienta, że ​​transakcja została pomyślnie zrealizowana.

Należy pamiętać, że w przypadku tego algorytmu utrata transakcji następuje tylko wtedy, gdy zarówno węzeł otrzymujący zmiany, jak i węzeł repliki zawiodą. Prawdopodobieństwo takiej awarii uważa się za niskie i ryzyko to jest akceptowane.

Jednak przy takim podejściu istnieje ryzyko odczytów fantomowych. Wyobraźmy sobie następujący scenariusz: w kroku 4 nie otrzymaliśmy potwierdzenia z żadnej repliki. Musimy wycofać tę transakcję i nie zwracać klientowi potwierdzenia. Ponieważ dane zostały zastosowane w kroku 2, pomiędzy zakończeniem kroku 2 a wycofaniem transakcji występuje przerwa czasowa, podczas której w transakcjach równoległych mogą pojawić się zmiany, które nie powinny znajdować się w bazie danych.

Replikacja półsynchroniczna bez strat

Jeśli trochę się zastanowisz, możesz po prostu odwrócić kroki algorytmu i rozwiązać problem odczytów fantomowych w tym scenariuszu:

  1. Rejestrowanie transakcji do dziennika bazy danych.
  2. Wysyłanie danych repliki.
  3. Otrzymanie potwierdzenia z repliki, że zmiany zostały otrzymane (zostaną zastosowane „kiedyś później”).
  4. Korzystanie z transakcji w silniku bazy danych.
  5. Zwrotne potwierdzenie do klienta, że ​​transakcja została pomyślnie zrealizowana.

Teraz zatwierdzamy zmiany tylko wtedy, gdy zostały zreplikowane.

Wniosek

Jak zawsze nie ma rozwiązań idealnych; istnieje zbiór rozwiązań, z których każde ma swoje zalety i wady i nadaje się do rozwiązywania różnych klas problemów. Jest to absolutnie prawdziwe w przypadku wyboru mechanizmu synchronizacji danych w replikowanej bazie danych. Zestaw zalet replikacji półsynchronicznej jest na tyle solidny i interesujący, że można go uznać za godny uwagi, pomimo jego niewielkiej rozpowszechnienia.

To wszystko. Do zobaczenia o godz kurs!

Źródło: www.habr.com

Dodaj komentarz