Interpretowanie odczytów i zapisów na dużą skalę

Zapoznaj się z tym dokumentem, aby podejmować świadome decyzje dotyczące projektowania aplikacji pod kątem wysokiej wydajności i niezawodności. Ten dokument zawiera zaawansowane tematy Cloud Firestore. Jeśli dopiero zaczynasz korzystać z Cloud Firestore, przeczytaj krótki przewodnik.

Cloud Firestore to elastyczna i skalowalna baza danych do tworzenia urządzeń mobilnych, stron internetowych i serwerów z użyciem Firebase i Google Cloud. Rozpoczęcie pracy w Cloud Firestore oraz pisanie zaawansowanych i zaawansowanych aplikacji jest bardzo łatwe.

Jeśli chcesz mieć pewność, że Twoje aplikacje będą nadal działać dobrze, a rozmiar bazy danych i zwiększy się ruch, warto poznać mechanikę odczytów i zapisów w backendzie Cloud Firestore. Musisz też zrozumieć interakcję odczytu i zapisu z warstwą pamięci masowej oraz poznać podstawowe ograniczenia, które mogą wpływać na wydajność.

Zanim zaprojektujesz architekturę swojej aplikacji, zapoznaj się z poniższymi sekcjami, aby poznać sprawdzone metody.

Omówienie komponentów ogólnych

Na diagramie poniżej widać ogólne komponenty uwzględnione w żądaniu do interfejsu Cloud Firestore API.

Komponenty wysokiego poziomu

Pakiet SDK Cloud Firestore i biblioteki klienta

Cloud Firestore obsługuje pakiety SDK i biblioteki klienta na różnych platformach. Aplikacja może wykonywać bezpośrednie wywołania HTTP i RPC do interfejsu Cloud Firestore API, natomiast biblioteki klienta zapewniają warstwę abstrakcji, która upraszcza korzystanie z interfejsu API i implementuje sprawdzone metody. Mogą one też zapewniać dodatkowe funkcje, takie jak dostęp offline, pamięci podręczne itp.

Google Front End (GFE)

Jest to usługa infrastruktury wspólna dla wszystkich usług Google Cloud. GFE akceptuje przychodzące żądania i przekazuje je do odpowiedniej usługi Google (w tym kontekście jest to usługa Firestore). Zapewnia też inne ważne funkcje, w tym ochronę przed atakami typu DoS.

Usługa Cloud Firestore

Usługa Cloud Firestore sprawdza żądanie do interfejsu API, w tym uwierzytelnianie, autoryzację, sprawdzanie limitów i reguły zabezpieczeń, a także zarządza transakcjami. Ta usługa Cloud Firestore zawiera klienta pamięci masowej, który współpracuje z warstwą pamięci masowej na potrzeby odczytów i zapisów danych.

Warstwa pamięci masowej w Cloud Firestore

Warstwa pamięci masowej Cloud Firestore odpowiada za przechowywanie danych i metadanych oraz powiązanych funkcji bazy danych udostępnianych przez Cloud Firestore. W poniższych sekcjach opisano, jak dane są uporządkowane w warstwie pamięci Cloud Firestore oraz jak skaluje się system. Poznanie sposobu porządkowania danych pomoże Ci zaprojektować skalowalny model danych i lepiej zrozumieć sprawdzone metody dotyczące Cloud Firestore.

Zakresy kluczy i podziały

Cloud Firestore to baza danych NoSQL zorientowana na dokumenty. Dane przechowujesz w dokumentach, które są uporządkowane w hierarchie kolekcji. Hierarchia kolekcji i identyfikator dokumentu są przekładane na jeden klucz dla każdego dokumentu. Przy użyciu tego jednego klucza dokumenty są logicznie przechowywane i uporządkowane leksykograficznie. Terminu „zakres kluczy” używamy w odniesieniu do leksykograficznie sąsiedniego zakresu kluczy.

Typowa baza danych Cloud Firestore jest zbyt duża, aby zmieścić się na jednej maszynie fizycznej. Zdarzają się też sytuacje, w których zbiór zadań związanych z danymi jest zbyt duży, aby 1 maszyna mogła je obsłużyć. Aby obsługiwać duże zbiory zadań, Cloud Firestore dzieli dane na partycje na osobne części, które można przechowywać na wielu komputerach lub serwerach pamięci i na nich udostępniać. Te partycje są tworzone w tabelach bazy danych w blokach zakresów kluczy zwanych podziałami.

Replikacja synchroniczna

Pamiętaj, że baza danych jest zawsze replikowana automatycznie i synchronicznie. Podziały danych mają repliki w różnych strefach, dzięki czemu są one dostępne nawet wtedy, gdy strefa jest niedostępna. Spójną replikacją do różnych kopii podziału zarządza algorytm Paxos na potrzeby konsensusu. 1 replika każdego przydziału wybiera się na lidera Paxos, który odpowiada za obsługę zapisów na tym podziale. Replikacja synchroniczna pozwala zawsze odczytywać najnowszą wersję danych z Cloud Firestore.

Efektem tego działania jest skalowalny i wysoce dostępny system, który zapewnia niewielkie opóźnienia zarówno w przypadku odczytów, jak i zapisów, niezależnie od dużych obciążeń i bardzo dużej skali.

Układ danych

Cloud Firestore to nieschematyczna baza danych dokumentów. Jednak wewnętrznie układa dane głównie w 2 tabelach relacyjnych baz danych w warstwie pamięci masowej w ten sposób:

  • Tabela Dokumenty: w tej tabeli przechowywane są dokumenty.
  • Tabela Indeksy: w tej tabeli przechowywane są wpisy indeksu, które umożliwiają efektywne uzyskiwanie wyników i posortowane według wartości indeksu.

Na diagramie poniżej widać, jak mogą wyglądać tabele bazy danych Cloud Firestore z podziałem na segmenty. podziały są powielane w 3 różnych strefach, a każdy z nich ma przypisanego lidera Paxos.

Układ danych

Jeden region a wiele regionów

Podczas tworzenia bazy danych musisz wybrać region lub wiele regionów.

Pojedyncza lokalizacja regionalna to określona lokalizacja geograficzna, np. us-west1. Jak objaśniono wcześniej, podziały danych w bazie danych Cloud Firestore mają repliki w różnych strefach w wybranym regionie.

Lokalizacja obejmująca wiele regionów składa się ze zdefiniowanego zbioru regionów, w których przechowywane są repliki bazy danych. We wdrożeniu Cloud Firestore w wielu regionach 2 z nich mają pełne repliki wszystkich danych w bazie danych. Trzeci region ma replikę poświadczającą, która nie utrzymuje pełnego zbioru danych, ale uczestniczy w replikacji. Dzięki replikacji danych między wieloma regionami można je zapisywać i odczytywać nawet w przypadku utraty całego regionu.

Więcej informacji o lokalizacjach w regionie znajdziesz w artykule Lokalizacje Cloud Firestore.

Jeden region a wiele regionów

Żywotność zapisu w Cloud Firestore

Klient Cloud Firestore może zapisywać dane przez utworzenie, zaktualizowanie lub usunięcie pojedynczego dokumentu. Zapis w jednym dokumencie wymaga atomowej aktualizacji zarówno dokumentu, jak i powiązanych z nim wpisów w indeksie w warstwie składowania danych. Cloud Firestore obsługuje również niepodzielne operacje polegające na wielu odczytach lub zapisach w co najmniej jednym dokumencie.

W przypadku wszystkich rodzajów zapisów Cloud Firestore zapewnia właściwości ACID (atomowość, spójność, izolację i trwałość) relacyjnych baz danych. Cloud Firestore zapewnia również serializowalność, co oznacza, że wszystkie transakcje są realizowane tak, jakby były wykonywane w serii.

Ogólne kroki transakcji zapisu

Gdy klient Cloud Firestore wyda lub zatwierdzi transakcję przy użyciu dowolnej z opisanych wcześniej metod, wewnętrznie jest to realizowane jako transakcja odczytu i zapisu w bazie danych w warstwie pamięci. Dzięki tej transakcji Cloud Firestore może udostępniać wymienione wcześniej właściwości ACID.

W pierwszym kroku transakcji Cloud Firestore odczytuje istniejący dokument i określa, jakie mutacje zostaną wprowadzone w danych w tabeli dokumentów.

Obejmuje to też wprowadzenie niezbędnych zmian w tabeli Indeksy w ten sposób:

  • Pola dodawane do dokumentów muszą zawierać odpowiednie wstawki w tabeli Indeksy.
  • Pola usuwane z dokumentów muszą zostać usunięte w tabeli Indeksy.
  • Pola, które są modyfikowane w dokumentach, wymagają usunięcia (w przypadku starych wartości) i wstawiania (w przypadku nowych wartości) w tabeli Indeksów.

Aby obliczyć mutacje wspomniane wcześniej, Cloud Firestore odczytuje konfigurację indeksowania dla projektu. Konfiguracja indeksowania zawiera informacje o indeksach projektu. Cloud Firestore używa 2 typów indeksów: jednego pola i złożonego. Szczegółowe informacje o indeksach tworzonych w Cloud Firestore znajdziesz w artykule Typy indeksów w Cloud Firestore.

Po obliczeniu mutacji Cloud Firestore zbiera je w transakcji, a następnie zatwierdza.

Zrozumienie transakcji zapisu w warstwie pamięci

Jak wspomnieliśmy wcześniej, zapis w Cloud Firestore obejmuje transakcję odczytu i zapisu w warstwie pamięci. W zależności od układu danych zapis może obejmować co najmniej jeden podział, tak jak w układzie danych.

Na poniższym diagramie baza danych Cloud Firestore ma 8 części (oznaczonych jako 1–8) hostowanych na 3 różnych serwerach pamięci masowej w 1 strefie. Każdy podział jest zreplikowany w co najmniej 3 różnych strefach. Każdy przydział ma swojego lidera Paxos, który może znajdować się w innej strefie w zależności od rodzaju podziału.

Podział bazy danych Cloud Firestore

Weźmy pod uwagę bazę danych Cloud Firestore, która zawiera kolekcję Restaurants w ten sposób:

Kolekcja restauracji

Klient Cloud Firestore żąda następującej zmiany w dokumencie w kolekcji Restaurant przez zaktualizowanie wartości pola priceCategory.

Zmień na dokument w kolekcji

Te ogólne kroki procesu opisują, co się dzieje podczas zapisu:

  1. Utwórz transakcję do odczytu i zapisu.
  2. Przeczytaj dokument restaurant1 w kolekcji Restaurants z tabeli Dokumenty z warstwy przechowywania.
  3. Z tabeli Indeksy odczytać indeksy dla dokumentu.
  4. Oblicza mutacje, które zostaną wprowadzone w danych. W tym przypadku występuje 5 mutacji:
    • M1: zaktualizuj wiersz pola restaurant1 w tabeli Dokumenty, aby odzwierciedlić zmianę wartości w polu priceCategory.
    • M2 i M3: usuń wiersze ze starą wartością priceCategory z tabeli Indeksy dla indeksów malejących i rosnących.
    • M4 i M5: wstaw wiersze z nową wartością parametru priceCategory w tabeli Indeksy, aby wyświetlić indeksy malejące i rosnące.
  5. Zatwierdź te mutacje.

Klient pamięci masowej w usłudze Cloud Firestore wyszukuje podziały, do których należą klucze wierszy do zmiany. Przeanalizujmy przypadek, w którym Podział 3 obsługuje M1, a Podział 6 – M2–M5. Występuje rozproszona transakcja, w której wszystkie te podziały są realizowane jako uczestnicy. Podziały uczestników może również obejmować dowolny inny podział, z którego dane zostały wcześniej odczytane w ramach transakcji odczytu i zapisu.

Co się dzieje w ramach zatwierdzenia:

  1. Klient pamięci masowej generuje zatwierdzenie. Zatwierdzenie zawiera mutacje M1–M5.
  2. Transakcje 3 i 6 biorą udział w tej transakcji. Jako koordynator wybiera się jeden z uczestników, np. Podział 3. Zadaniem koordynatora jest dopilnowanie, aby transakcja była zatwierdzana lub przerywana atomowo u wszystkich uczestników.
    • Repliki liderów tych podziałów odpowiadają za pracę wykonaną przez uczestników i koordynatorów.
  3. Każdy uczestnik i koordynator przeprowadza algorytm Paxos przy użyciu odpowiednich replik.
    • Z replikami lider stosuje algorytm Paxos. Kworum jest wyższa, jeśli większość replik udzieli odpowiedzi ok to commit dla repliki wiodącej.
    • Następnie każdy uczestnik powiadamia koordynatora o przygotowaniu (pierwsza faza zobowiązania dwuetapowego). Jeśli żaden z uczestników nie może zrealizować transakcji, cała transakcja jest aborts.
  4. Gdy koordynator wie, że wszyscy uczestnicy, w tym samego siebie, są przygotowani, informuje wszystkich uczestników o wyniku transakcji accept (druga faza zatwierdzenia dwufazowego). Na tym etapie każdy uczestnik zapisuje decyzję dotyczącą zatwierdzenia w postaci stabilnej pamięci masowej, a transakcja zostaje zatwierdzona.
  5. Koordynator odpowiada klientowi pamięci masowej w Cloud Firestore, że transakcja została zatwierdzona. Równolegle koordynator i wszyscy uczestnicy stosują mutacje do danych.

Cykl życia zatwierdzenia

Gdy baza danych Cloud Firestore jest mała, może się zdarzyć, że do jednego podziału będą należeć wszystkie klucze w mutacjach M1–M5. W takim przypadku w transakcji jest tylko 1 uczestnik, a wspomniane wcześniej zatwierdzenie dwufazowe nie jest wymagane, co przyspiesza zapisy.

Zapisywanie w wielu regionach

W przypadku wdrożenia w wielu regionach rozłożenie replik w różnych regionach zwiększa dostępność, ale wiąże się z kosztem wydajności. Komunikacja między replikami w różnych regionach trwa dłużej. W związku z tym podstawowy czas oczekiwania w przypadku operacji w Cloud Firestore jest nieco dłuższy niż w przypadku wdrożeń w jednym regionie.

Konfigurujemy repliki w taki sposób, aby liderzy w przypadku podziałów zawsze pozostawali w regionie głównym. Region podstawowy to ten, z którego przychodzi ruch do serwera Cloud Firestore. Taka decyzja kierownictwa ogranicza opóźnienie w obie strony komunikacji między klientem pamięci masowej w Cloud Firestore a liderem repliki (lub koordynatorem transakcji z wieloma fragmentami).

Każdy zapis w Cloud Firestore wymaga też pewnej interakcji z mechanizmem czasu rzeczywistego w Cloud Firestore. Więcej informacji o zapytaniach w czasie rzeczywistym znajdziesz w artykule Omówienie zapytań w czasie rzeczywistym na dużą skalę.

Przebieg odczytu w Cloud Firestore

W tej sekcji omawiamy niezależne odczyty nie w czasie rzeczywistym w Cloud Firestore. Wewnętrznie serwer Cloud Firestore obsługuje większość tych zapytań na 2 głównych etapach:

  1. Skanowanie jednego zakresu w tabeli Indeksy
  2. wyszukiwania punktów w tabeli Dokumenty na podstawie wyniku wcześniejszego skanowania,
Niektóre zapytania w Cloud Firestore mogą wymagać mniejszej liczby operacji przetwarzania lub większej ich ilości (np. zapytań IN).

Odczyty danych z warstwy pamięci masowej są wykonywane wewnętrznie za pomocą transakcji bazy danych w celu zapewnienia spójnych odczytów. Jednak w przeciwieństwie do transakcji używanych do zapisu, transakcje te nie podlegają blokadom. Zamiast tego wybierają sygnaturę czasową, a następnie wykonują wszystkie odczyty z tą sygnaturą czasową. Nie nakładają blokad, więc nie blokują równoczesnych transakcji odczytu i zapisu. Aby zrealizować tę transakcję, klient pamięci masowej w Cloud Firestore określa powiązanie z sygnaturą czasową, która informuje warstwę pamięci, jak wybrać sygnaturę czasową odczytu. Typ powiązanej sygnatury czasowej wybrany przez klienta pamięci w Cloud Firestore jest określany przez opcje odczytu żądania odczytu.

Omówienie transakcji odczytu w warstwie pamięci

W tej sekcji opisano typy odczytów i sposób ich przetwarzania w warstwie pamięci masowej w Cloud Firestore.

Mocne lektury

Domyślnie odczyty z Cloud Firestore są zdecydowanie spójne. Ta silna spójność oznacza, że odczyt z Cloud Firestore zwraca najnowszą wersję danych, która odzwierciedla wszystkie zapisy, które zostały zatwierdzone aż do rozpoczęcia odczytu.

Pojedynczy odczyt

Klient pamięci masowej w Cloud Firestore wyszukuje podziały, do których należą klucze wierszy do odczytu. Załóżmy, że musi odczytać informacje z rozdziału 3 z wcześniejszej sekcji. Klient wysyła żądanie odczytu do najbliższej repliki, aby zmniejszyć czas oczekiwania w obie strony.

W tym momencie w zależności od wybranej repliki mogą wystąpić te przypadki:

  • Żądanie odczytu trafia do repliki wiodącej (strefa A).
    • Lider jest zawsze aktualny, więc zapis można kontynuować bezpośrednio.
  • Żądanie odczytu trafia do repliki innej niż replika wiodąca (na przykład do strefy B)
    • Podział 3 może określić na podstawie swojego stanu wewnętrznego, że ma wystarczającą ilość informacji do obsłużenia odczytu.
    • Podział 3 nie ma pewności, czy ma najnowsze dane. Wysyła wiadomość do lidera z prośbą o podanie sygnatury czasowej ostatniej transakcji, która musi zostać zastosowana w celu wyświetlenia odczytu. Po jej zakończeniu odczyt może być kontynuowany.

Cloud Firestore zwraca wtedy odpowiedź do klienta.

Odczyt wielokrotny

W sytuacji, gdy odczyty muszą być wykonywane w ramach wielu podziałów, na każdym z nich działa ten sam mechanizm. Gdy dane zostaną zwrócone ze wszystkich podziałów, klient pamięci masowej w Cloud Firestore połączy wyniki. Następnie Cloud Firestore przekazuje te dane klientowi.

Nieaktualne odczyty

Silne odczyty to domyślny tryb w Cloud Firestore. Wiąże się to jednak z potencjalnie dłuższym czasem oczekiwania ze względu na komunikację, która może być wymagana z liderem. Często aplikacja Cloud Firestore nie musi odczytywać najnowszych wersji danych, a jej funkcje dobrze działają z danymi, które mogą być nieaktualne (kilka sekund).

W takim przypadku klient może zdecydować się na otrzymywanie nieaktualnych odczytów, używając opcji odczytu read_time. W tym przypadku odczyty są wykonywane, ponieważ dane były w lokalizacji read_time, a najbliższa replika prawdopodobnie już potwierdziła, że posiada dane w określonym read_time. Dla uzyskania zauważalnego wzrostu skuteczności 15 sekund to rozsądna wartość braku aktualizacji. Nawet w przypadku nieaktualnych odczytów wygenerowane wiersze są ze sobą zgodne.

Unikaj hotspotów

Podziały w Cloud Firestore są automatycznie podzielone na mniejsze części, aby w razie potrzeby lub gdy przestrzeń kluczy się powiększyła, a ruch obsługiwano na kolejnych serwerach pamięci masowej. Podziały utworzone w celu obsługi nadmiernego ruchu są przechowywane przez około 24 godziny, nawet jeśli ruch zniknie. Jeśli więc następują cykliczne skoki ruchu, podziały zostają utrzymywane, a w razie potrzeby wprowadzamy więcej podziałów. Te mechanizmy pomagają bazom danych Cloud Firestore na autoskalowanie zgodnie ze rosnącym obciążeniem ruchu lub rozmiarem bazy danych. Istnieją jednak pewne ograniczenia, o których należy pamiętać, opisane poniżej.

Podział miejsca na dane i obciążenia wymaga czasu, a zbyt szybkie zwiększanie ruchu może powodować błędy związane z dużym czasem oczekiwania lub przekroczeniem terminu, często nazywane hotspotami, gdy usługa dostosowuje się do zmian. Sprawdzoną metodą jest rozdzielenie operacji w obrębie zakresu kluczy, jednocześnie zwiększając ruch związany z kolekcją w bazie danych z liczbą 500 operacji na sekundę. Następnie co 5 minut zwiększaj ruch nawet o 50%. Ten proces jest nazywany regułą 500/50/5 i pozycjonuje bazę danych w taki sposób, aby optymalnie skalowała się w celu wykonania zadania.

Chociaż podziały są tworzone automatycznie przy rosnącym obciążeniu, Cloud Firestore może dzielić zakres kluczy tylko do momentu, gdy wyświetli pojedynczy dokument przy użyciu dedykowanego zestawu zreplikowanych serwerów pamięci masowej. W związku z tym duża i długotrwała liczba równoczesnych operacji na 1 dokumencie może doprowadzić do utworzenia ważnego obszaru na tym dokumencie. Jeśli w jednym dokumencie występują długotrwałe opóźnienia, rozważ zmianę modelu danych, aby podzielić lub zreplikować dane w wielu dokumentach.

Błędy rywalizacji występują, gdy wiele operacji próbuje jednocześnie odczytać lub zapisać ten sam dokument.

Kolejnym szczególnym przypadkiem użycia hotspotu jest sytuacja, w której jako identyfikator dokumentu w Cloud Firestore używany jest sekwencyjny klucz rosnący/malejący i występuje znacznie duża liczba operacji na sekundę. Większa liczba podziałów nie pomaga, ponieważ gwałtowny wzrost ruchu po prostu przenosi się do nowo utworzonego podziału. Cloud Firestore domyślnie indeksuje wszystkie pola w dokumencie, dlatego takie ruchome hotspoty mogą również zostać utworzone w przestrzeni indeksu dla pola dokumentu zawierającego stale rosnącą lub malejącą wartość, np. sygnaturę czasową.

Korzystając z opisanych powyżej metod, Firestore może obsługiwać dowolnie duże zbiory zadań bez konieczności dostosowywania żadnej konfiguracji.

Rozwiązywanie problemów

Firestore udostępnia Key Visualizer jako narzędzie diagnostyczne przeznaczone do analizowania wzorców użytkowania i rozwiązywania problemów z hotspotami.

Co dalej