Dlaczego może być potrzebna półsynchroniczna replikacja?

Cześć wszystkim. Z tej strony Władysław Rodin. Obecnie uczę na portalu OTUS kursów dotyczących architektury oprogramowania i architektury oprogramowania pod dużym obciążeniem. W przeddzień rozpoczęcia nowej edycji kursu „Architekt wysokich obciążeń” postanowiłem napisać krótki autorski materiał, którym chcę się z wami podzielić.

Dlaczego może być potrzebna półsynchroniczna replikacja?

Wprowadzenie

Z powodu ograniczeń HDD, które mogą obsługiwać jedynie około 400-700 operacji na sekundę (co jest nieporównywalne z typowymi rps dla systemu o wysokim obciążeniu), klasyczna baza danych staje się wąskim gardłem architektury. Dlatego należy poświęcić szczególną uwagę wzorom skalowania tego magazynu.

Na obecny moment są dwa wzory skalowania bazy: replikacja oraz shardowanie. Shardowanie pozwala na skalowanie operacji zapisu, co w efekcie zmniejsza rps zapisu na jeden serwer w twoim klastrze. Replikacja umożliwia to samo, ale w przypadku operacji odczytu. Właśnie temu wzorowi poświęcony jest ten artykuł.

Replikacja

Patrząc na replikację z wysokiego poziomu, to prosta sprawa: miałeś jeden serwer, na którym były dane, a następnie ten serwer przestał sobie radzić z obciążeniem podczas odczytu tych danych. Dodajesz kilka serwerów, synchronizujesz dane na wszystkich serwerach, a użytkownik może odczytywać z dowolnego serwera swojego klastra.

Pomimo pozornej prostoty, istnieje kilka klasyfikacji różnych realizacji tego schematu:

  • Według ról w klastrze (master-master lub master-slave)
  • Według przesyłanych obiektów (oparte na wierszach, oparte na instrukcjach lub mieszane)
  • Według mechanizmu synchronizacji węzłów

Dziś zajmiemy się właśnie 3. punktem.

Jak przebiega realizacja transakcji

Temat ten nie dotyczy bezpośrednio replikacji, można o nim napisać oddzielny artykuł, jednak ponieważ zrozumienie mechanizmu realizacji transakcji jest kluczowe, pozwolę sobie przypomnieć najważniejsze kwestie. Realizacja transakcji przebiega w 3 etapach:

  1. Rejestracja transakcji w dzienniku bazy danych.
  2. Zastosowanie transakcji w silniku bazy danych.
  3. Zwrócenie potwierdzenia klientowi o pomyślnym zastosowaniu transakcji.

W różnych bazach w tym algorytmie mogą występować niuanse: na przykład w silniku InnoDB bazie MySQL są 2 dzienniki: jeden do replikacji (binary log), a drugi do utrzymania ACID (undo/redolog), podczas gdy w PostgreSQL jest tylko jeden dziennik, który spełnia obie funkcje (write ahead log = WAL). Jednak powyżej przedstawiona jest ogólna koncepcja, która pozwala nie brać pod uwagę tych niuansów.

Replikacja synchronizacyjna (sync)

Dodajmy do algorytmu zatwierdzania transakcji logikę replikacji uzyskanych zmian:

  1. Rejestracja transakcji w dzienniku bazy danych.
  2. Zastosowanie transakcji w silniku bazy danych.
  3. Wysyłanie danych do wszystkich replik.
  4. Otrzymywanie potwierdzenia od wszystkich replik o wykonaniu na nich transakcji.
  5. Zwrócenie potwierdzenia klientowi o pomyślnym zastosowaniu transakcji.

Przy tym podejściu napotykamy na szereg niedogodności:

  • klient czeka na zastosowanie zmian na wszystkich replikach.
  • wraz ze wzrostem liczby węzłów w klastrze zmniejszamy prawdopodobieństwo, że operacja zapisu zakończy się sukcesem.

Jeśli pierwszy punkt jest w miarę jasny, to przyczyny drugiego punktu warto wyjaśnić. Jeśli podczas replikacji synchronizacyjnej nie otrzymamy odpowiedzi od przynajmniej jednego węzła, cofnę transakcję. Zwiększając liczbę węzłów w klastrze, zwiększasz prawdopodobieństwo, że operacja zapisu się nie powiedzie.

Czy możemy czekać na potwierdzenie tylko od jakiejś części węzłów, na przykład od 51% (kworum)? Tak, możemy, jednak w klasycznej wersji wymagane jest potwierdzenie od wszystkich węzłów, ponieważ w ten sposób możemy zapewnić pełną spójność danych w klastrze, co stanowi niewątpliwą zaletę tego typu replikacji.

Replikacja asynchronizacyjna (async)

Zmieńmy poprzedni algorytm. Dane do replik będziemy wysyłać „kiedyś później”, a „kiedyś później” zmiany zostaną zastosowane na replikach:

  1. Rejestracja transakcji w dzienniku bazy danych.
  2. Zastosowanie transakcji w silniku bazy danych.
  3. Zwrócenie potwierdzenia klientowi o pomyślnym zastosowaniu transakcji.
  4. Wysyłanie danych do replik i zastosowanie zmian przez nie.

To podejście prowadzi do tego, że klaster działa szybko, ponieważ nie trzymamy klienta w oczekiwaniu, aż dane dotrą do replik i zostaną zatwierdzone.

Jednakże warunek przesyłania danych do replik „kiedyś później” może prowadzić do utraty transakcji, zwłaszcza utraty potwierdzonej użytkownikowi transakcji, ponieważ jeśli dane nie zdążą się zreplikować, a potwierdzenie o pomyślnym wykonaniu operacji zostało wysłane do klienta, a węzeł, do którego wpłynęły zmiany, ma problem z dyskiem HDD, tracimy transakcję, co może prowadzić do bardzo nieprzyjemnych konsekwencji.

Replikacja półsynchroniczna (semisync)

W końcu dotarliśmy do replikacji pół-synchronnej. Ten typ replikacji nie jest zbyt znany i nie jest zbyt powszechny, jednak stanowi znaczące zainteresowanie, ponieważ może połączyć zalety zarówno replikacji synchronnej, jak i asynchronnej.

Spróbujemy połączyć 2 poprzednie podejścia. Nie będziemy długo trzymać klienta, ale zażądamy, aby dane zostały zreplikowane:

  1. Rejestracja transakcji w dzienniku bazy danych.
  2. Zastosowanie transakcji w silniku bazy danych.
  3. Wysyłanie danych do replik.
  4. Otrzymanie potwierdzenia od repliki o otrzymaniu zmian (zastosowane będą «kiedyś później»).
  5. Zwrócenie potwierdzenia klientowi o pomyślnym zastosowaniu transakcji.

Zauważ, że przy takim algorytmie utrata transakcji występuje tylko w przypadku awarii zarówno węzła przyjmującego zmiany, jak i węzła-repliki. Prawdopodobieństwo takiej awarii uznaje się za małe i te ryzyka są akceptowane.

Jednak w tym podejściu istnieje ryzyko tzw. phantom reads. Wyobraźmy sobie następujący scenariusz: na kroku 4 nie otrzymaliśmy potwierdzenia od żadnej repliki. Musimy cofnąć tę transakcję, a klientowi nie zwracać potwierdzenia. Ponieważ dane zostały zastosowane na kroku 2, pomiędzy zakończeniem kroku 2 a cofaniem transakcji powstaje tymczasowy luk, w trakcie którego równoległe transakcje mogą zobaczyć te zmiany, których nie powinno być w bazie.

Replikacja lose-less semisync

Jeśli się chwilę zastanowić, można jedynie zamienić miejscami kroki algorytmu, aby rozwiązać problem phantom reads w tym scenariuszu:

  1. Rejestracja transakcji w dzienniku bazy danych.
  2. Wysyłka danych do repliki.
  3. Otrzymanie potwierdzenia od repliki o otrzymaniu zmian (zastosowane będą «kiedyś później»).
  4. Zastosowanie transakcji w silniku bazy danych.
  5. Zwrócenie potwierdzenia klientowi o pomyślnym zastosowaniu transakcji.

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

Wnioski

Jak zawsze, nie ma idealnych rozwiązań, istnieje zbiór rozwiązań, z których każde ma swoje zalety i wady oraz nadaje się do rozwiązania różnych klas problemów. To dotyczy również wyboru mechanizmu synchronizacji danych w replikowanej bazie danych. Zestaw zalet, jakie posiada replikacja pół-synchroniczna, jest wystarczająco solidny i interesujący, aby zasługiwał na uwagę, mimo że jest rzadko stosowany.

Na tym kończymy. Do zobaczenia na kursie!

Ź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