Odczyty i zapisy na dużą skalę

Przeczytaj ten dokument, aby podejmować świadome decyzje dotyczące projektowania architektury aplikacji pod kątem wysokiej wydajności i niezawodności. Ten dokument zawiera zaawansowane tematy dotyczące Cloud Firestore. Jeśli dopiero zaczynasz korzystać z Cloud Firestore, zapoznaj się z krótkim przewodnikiem.

Cloud Firestore to elastyczna, skalowalna baza danych do tworzenia aplikacji mobilnych, internetowych i serwerowych, oferowana przez Firebase i Google Cloud. Rozpoczęcie pracy z Cloud Firestore i pisanie rozbudowanych, zaawansowanych aplikacji jest bardzo proste.

Aby mieć pewność, że aplikacje będą działać prawidłowo, gdy rozmiar bazy danych i ruch będą się zwiększać, warto zrozumieć mechanizmy odczytu i zapisu w backendzie Cloud Firestore. Musisz też zrozumieć interakcję odczytów i zapisów z warstwą pamięci masowej oraz podstawowe ograniczenia, które mogą wpływać na wydajność.

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

Poznanie komponentów wysokiego poziomu

Na diagramie poniżej widać komponenty wysokiego poziomu, które są zaangażowane w żądanie do interfejsu API Cloud Firestore.

Komponenty wysokiego poziomu

Pakiety SDK i biblioteki klienta Cloud Firestore

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 API Cloud Firestore, ale biblioteki klienta zapewniają warstwę abstrakcji, która upraszcza korzystanie z interfejsu API i wdrażanie sprawdzonych metod. Mogą też udostępniać dodatkowe funkcje, takie jak dostęp offline, pamięć podręczna itp.

Google Front End (GFE)

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

Usługa Cloud Firestore

Usługa Cloud Firestore przeprowadza kontrole żądania do interfejsu API, które obejmują uwierzytelnianie, autoryzację, sprawdzanie limitów i reguły zabezpieczeń, a także zarządza transakcjami. Usługa Cloud Firestore obejmuje klienta pamięci masowej, który współdziała z warstwą pamięci masowej w celu odczytu i zapisu danych.

Warstwa pamięci masowej 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 sekcjach poniżej opisano, jak dane są uporządkowane w warstwie pamięci masowej Cloud Firestore i jak system się skaluje. Wiedza o tym, jak są uporządkowane dane, może pomóc Ci zaprojektować skalowalny model danych i lepiej zrozumieć sprawdzone metody w Cloud Firestore.

Zakresy klawiszy i podziały

Cloud Firestore to baza danych NoSQL zorientowana na dokumenty. Dane są przechowywane w dokumentach, które są uporządkowane w hierarchie kolekcji. Hierarchia kolekcji i identyfikator dokumentu są tłumaczone na pojedynczy klucz dla każdego dokumentu. Dokumenty są przechowywane logicznie i uporządkowane leksykograficznie według tego pojedynczego klucza. Termin „zakres kluczy” odnosi się do leksykograficznie ciągłego zakresu kluczy.

Typowa baza danych Cloud Firestore jest zbyt duża, aby zmieścić się na jednym komputerze fizycznym. Zdarzają się też sytuacje, gdy obciążenie danymi jest zbyt duże, aby jedna maszyna mogła je obsłużyć. Aby obsługiwać duże obciążenia, Cloud Firestore dzieli dane na osobne części, które mogą być przechowywane na wielu maszynach lub serwerach pamięci masowej i z nich udostępniane. Te partycje są tworzone w tabelach bazy danych w blokach zakresów kluczy nazywanych 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ą dostępne nawet wtedy, gdy strefa stanie się niedostępna. Spójna replikacja do różnych kopii podziału jest zarządzana przez algorytm Paxos, który zapewnia konsensus. Jedna replika każdego podziału jest wybierana jako lider Paxos, który odpowiada za obsługę zapisów w tym podziale. Replikacja synchroniczna umożliwia odczytywanie najnowszej wersji danych z Cloud Firestore.

W rezultacie otrzymujemy skalowalny system o wysokiej dostępności, który zapewnia krótki czas oczekiwania zarówno w przypadku odczytu, jak i zapisu, niezależnie od dużych obciążeń i bardzo dużej skali.

Układ danych

Cloud Firestore to bezschematowa baza danych dokumentów. Jednak wewnętrznie dane są rozmieszczone głównie w 2 tabelach w stylu relacyjnej bazy danych w warstwie pamięci masowej:

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

Poniższy diagram pokazuje, jak mogą wyglądać tabele bazy danych Cloud Firestore z podziałami. Podziały są replikowane w 3 różnych strefach, a każdy podział 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 konkretne położenie geograficzne, np. us-west1. Jak wyjaśniono wcześniej, fragmenty danych bazy danych Cloud Firestore mają repliki w różnych strefach w wybranym regionie.

Lokalizacja obejmująca wiele regionów składa się ze zdefiniowanego zestawu regionów, w których są przechowywane repliki bazy danych. W przypadku wdrożenia Cloud Firestore w wielu regionach 2 z nich mają pełne repliki wszystkich danych w bazie danych. Trzeci region ma replikę świadka, która nie przechowuje pełnego zbioru danych, ale uczestniczy w replikacji. Dzięki replikacji danych między wieloma regionami dane są dostępne do zapisu i odczytu nawet w przypadku utraty całego regionu.

Więcej informacji o lokalizacjach w regionie znajdziesz w artykule na temat lokalizacji Cloud Firestore.

Jeden region a wiele regionów

Informacje o cyklu życia zapisu w Cloud Firestore

Klient Cloud Firestore może zapisywać dane, tworząc, aktualizując lub usuwając pojedynczy dokument. Zapis w pojedynczym dokumencie wymaga niepodzielnej aktualizacji zarówno dokumentu, jak i powiązanych z nim wpisów indeksu w warstwie pamięci masowej. Cloud Firestore obsługuje też operacje niepodzielne, które składają się z wielu odczytów lub zapisów w co najmniej 1 dokumencie.

W przypadku wszystkich rodzajów zapisu Cloud Firestore zapewnia właściwości ACID (atomicity, consistency, isolation, and durability – niepodzielność, spójność, izolacja i trwałość) relacyjnych baz danych. Cloud Firestore zapewnia też serializowalność, co oznacza, że wszystkie transakcje są wykonywane w kolejności szeregowej.

Najważniejsze kroki transakcji zapisu

Gdy klient Cloud Firestore wysyła żądanie zapisu lub zatwierdza transakcję za pomocą dowolnej z wymienionych wcześniej metod, jest to wykonywane wewnętrznie jako transakcja odczytu i zapisu bazy danych w warstwie pamięci masowej. Transakcja umożliwia Cloud Firestore zapewnienie wspomnianych wcześniej właściwości ACID.

W pierwszym kroku transakcji Cloud Firestore odczytuje istniejący dokument i określa mutacje, które należy wprowadzić w danych w tabeli Documents.

Obejmuje to również wprowadzenie niezbędnych zmian w tabeli Indeksy w następujący sposób:

  • Pola dodawane do dokumentów wymagają odpowiednich wstawień w tabeli Indeksy.
  • Pola, które są usuwane z dokumentów, muszą zostać usunięte z tabeli indeksów.
  • Pola, które są modyfikowane w dokumentach, wymagają zarówno usunięcia (w przypadku starych wartości), jak i wstawienia (w przypadku nowych wartości) w tabeli indeksów.

Aby obliczyć wspomniane wcześniej mutacje, Cloud Firestore odczytuje konfigurację indeksowania bazy danych. Konfiguracja indeksowania przechowuje informacje o indeksach bazy danych. Cloud Firestore używa 2 typów indeksów: indeksów pojedynczych pól i indeksów złożonych. Szczegółowe informacje o indeksach utworzonych w Cloud Firestore znajdziesz w artykule Typy indeksów w Cloud Firestore.

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

Informacje o transakcji zapisu w warstwie pamięci masowej

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

Na poniższym diagramie baza danych Cloud Firestore ma 8 podziałów (oznaczonych cyframi 1–8) hostowanych na 3 różnych serwerach pamięci masowej w jednej strefie, a każdy podział jest replikowany w co najmniej 3 różnych strefach. Każdy podział ma lidera Paxos, który może znajdować się w innej strefie w przypadku różnych podziałów.

<span class=Podział bazy danych Cloud Firestore">

Rozważmy bazę danych Cloud Firestore, która zawiera kolekcję Restaurants:

Kolekcja restauracji

Klient Cloud Firestore prosi o wprowadzenie tej zmiany w dokumencie w kolekcji Restaurant przez zaktualizowanie wartości pola priceCategory.

Zmiana w dokumencie w kolekcji

Poniżej opisujemy ogólne kroki, które są wykonywane w ramach zapisu:

  1. Utwórz transakcję zapisu i odczytu.
  2. Odczytaj dokument restaurant1 w kolekcji Restaurants z tabeli Dokumenty z warstwy pamięci masowej.
  3. Odczytaj indeksy dokumentu z tabeli Indeksy.
  4. Oblicz mutacje, które mają zostać wprowadzone w danych. W tym przypadku występuje 5 mutacji:
    • M1: Zaktualizuj wiersz dotyczący restaurant1 w tabeli Dokumenty, aby odzwierciedlić zmianę wartości pola priceCategory.
    • M2 i M3: usuń wiersze ze starą wartością priceCategory w tabeli Indeksy dla indeksów malejących i rosnących.
    • M4 i M5: wstaw wiersze dla nowej wartości priceCategory w tabeli Indeksy dla indeksów malejących i rosnących.
  5. Zatwierdź te mutacje.

Klient pamięci masowej w usłudze Cloud Firestore wyszukuje podziały, które są właścicielami kluczy wierszy do zmiany. Rozważmy przypadek, w którym podział 3 obsługuje M1, a podział 6 obsługuje M2–M5. Istnieje transakcja rozproszona, która obejmuje wszystkie te podziały jako uczestników. Podziały uczestników mogą też obejmować inne podziały, z których dane zostały odczytane wcześniej w ramach transakcji odczytu i zapisu.

Poniżej opisujemy, co się dzieje w ramach zatwierdzania:

  1. Klient pamięci masowej wysyła zatwierdzenie. Zatwierdzenie zawiera mutacje M1–M5.
  2. Uczestnikami tej transakcji są podziały 3 i 6. Jeden z uczestników jest wybierany jako koordynator, np. Split 3. Zadaniem koordynatora jest zapewnienie, że transakcja zostanie zatwierdzona lub przerwana niepodzielnie u wszystkich uczestników.
    • Repliki liderów tych podziałów odpowiadają za pracę wykonywaną przez uczestników i koordynatorów.
  3. Każdy uczestnik i koordynator uruchamia algorytm Paxos z odpowiednimi replikami.
    • Lider uruchamia algorytm Paxos z replikami. Kworum zostaje osiągnięte, gdy większość replik odpowie liderowi odpowiedzią ok to commit.
    • Następnie każdy uczestnik powiadamia koordynatora, gdy jest przygotowany (pierwsza faza zatwierdzania dwufazowego). Jeśli którykolwiek z uczestników nie może zatwierdzić transakcji, cała transakcja aborts.
  4. Gdy koordynator wie, że wszyscy uczestnicy, w tym on sam, są przygotowani, przekazuje wynik transakcji accept wszystkim uczestnikom (druga faza zatwierdzania dwufazowego). W tej fazie każdy uczestnik zapisuje decyzję o zatwierdzeniu w stabilnej pamięci masowej, a transakcja zostaje zatwierdzona.
  5. Koordynator odpowiada klientowi pamięci masowej w Cloud Firestore, że transakcja została zatwierdzona. Jednocześnie koordynator i wszyscy uczestnicy stosują mutacje do danych.

Cykl życia zatwierdzenia

Jeśli baza danych Cloud Firestore jest mała, może się zdarzyć, że pojedynczy podział będzie zawierał wszystkie klucze w mutacjach M1–M5. W takim przypadku w transakcji uczestniczy tylko jeden uczestnik i wspomniane wcześniej zatwierdzenie dwufazowe nie jest wymagane, co przyspiesza zapisywanie.

Zapisy w wielu regionach

W przypadku wdrożenia w wielu regionach rozproszenie replik w różnych regionach zwiększa dostępność, ale wiąże się z kosztami wydajności. Komunikacja między replikami w różnych regionach wymaga dłuższego czasu błądzenia. Z tego powodu podstawowy czas oczekiwania w przypadku operacji Cloud Firestore jest nieco dłuższy niż w przypadku wdrożeń w jednym regionie.

Konfigurujemy repliki w taki sposób, aby liderzy podziałów zawsze pozostawali w regionie podstawowym. Region główny to region, z którego ruch przychodzi na serwer Cloud Firestore. Ta decyzja dotycząca lidera zmniejsza opóźnienie w obie strony w komunikacji między klientem pamięci masowej w regionie Cloud Firestore a liderem repliki (lub koordynatorem transakcji z wieloma podziałami).

Każdy zapis w Cloud Firestore wiąże się też z interakcją z mechanizmem czasu rzeczywistego w Cloud Firestore. Więcej informacji o zapytaniach w czasie rzeczywistym znajdziesz w artykule Zapytania w czasie rzeczywistym na dużą skalę.

Informacje o życiu odczytu w aplikacji Cloud Firestore

W tej sekcji omówimy samodzielne odczyty w czasie innym niż rzeczywisty w Cloud Firestore. Wewnętrznie serwer Cloud Firestore obsługuje większość tych zapytań w 2 głównych etapach:

  1. Pojedyncze skanowanie zakresu w tabeli Indexes
  2. wyszukiwania punktowe w tabeli Documents na podstawie wyniku wcześniejszego skanowania.
Niektóre zapytania mogą wymagać mniejszego lub większego przetwarzania (np. zapytania IN) w Cloud Firestore.

Odczyty danych z warstwy pamięci masowej są wykonywane wewnętrznie przy użyciu transakcji bazy danych, aby zapewnić spójne odczyty. Jednak w przeciwieństwie do transakcji używanych do zapisu te transakcje nie blokują danych. Zamiast tego wybierają sygnaturę czasową, a następnie wykonują wszystkie odczyty z tą sygnaturą czasową. Ponieważ nie uzyskują blokad, nie blokują równoczesnych transakcji odczytu i zapisu. Aby wykonać tę transakcję, klient pamięci masowej w Cloud Firestore określa granicę sygnatury czasowej, która wskazuje warstwie pamięci masowej, jak wybrać sygnaturę czasową odczytu. Typ granicy sygnatury czasowej wybrany przez klienta pamięci masowej w Cloud Firestore jest określany przez opcje odczytu dla żądania odczytu.

Informacje o transakcji odczytu w warstwie pamięci masowej

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

Silne odczyty

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

Odczyt pojedynczego podziału

Klient pamięci masowej w Cloud Firestore wyszukuje podziały, które są właścicielami kluczy wierszy do odczytu. Załóżmy, że musi odczytać dane z podziału 3 z poprzedniej sekcji. Klient wysyła żądanie odczytu do najbliższej repliki, aby skrócić czas oczekiwania w obie strony.

W tym momencie mogą wystąpić następujące sytuacje w zależności od wybranej repliki:

  • Żądanie odczytu trafia do repliki lidera (strefa A).
    • Ponieważ lider jest zawsze aktualny, odczyt może być wykonywany bezpośrednio.
  • Żądanie odczytu trafia do repliki, która nie jest liderem (np. w strefie B).
    • Fragment 3 może wiedzieć na podstawie swojego stanu wewnętrznego, że ma wystarczająco dużo informacji, aby obsłużyć odczyt, i to robi.
    • Split 3 nie ma pewności, czy widzi najnowsze dane. Wysyła wiadomość do lidera z prośbą o sygnaturę czasową ostatniej transakcji, którą musi zastosować, aby obsłużyć odczyt. Po zastosowaniu tej transakcji można kontynuować odczyt.

Cloud Firestore zwraca odpowiedź do klienta.

Odczyt z wielu podziałów

W sytuacji, gdy odczyty muszą być wykonywane z wielu podziałów, ten sam mechanizm występuje we wszystkich podziałach. Gdy dane zostaną zwrócone ze wszystkich podziałów, klient pamięci masowej w Cloud Firestore połączy wyniki. Cloud Firestore odpowiada klientowi, podając te dane.

Odczyty opóźnione

Silne odczyty to domyślny tryb w Cloud Firestore. Wiąże się to jednak z potencjalnie większym opóźnieniem ze względu na komunikację, która może być wymagana z liderem. Często aplikacja Cloud Firestore nie musi odczytywać najnowszej wersji danych, a funkcja działa dobrze z danymi, które mogą być nieaktualne o kilka sekund.

W takim przypadku klient może zdecydować się na odczyty opóźnione, korzystając z opcji odczytu read_time. W tym przypadku odczyty są wykonywane tak, jakby dane były z read_time, a najbliższa replika najprawdopodobniej już zweryfikowała, że ma dane z określonego read_time. Aby uzyskać zauważalnie lepszą wydajność, warto ustawić wartość nieaktualności na 15 sekund. Nawet w przypadku odczytów opóźnionych zwrócone wiersze są ze sobą spójne.

Unikaj gorących punktów

Podziały w Cloud Firestore są automatycznie dzielone na mniejsze części, aby w razie potrzeby lub po rozszerzeniu przestrzeni kluczy rozdzielić obsługę ruchu na więcej serwerów pamięci masowej. Podziały utworzone w celu obsługi nadmiernego ruchu są zachowywane przez około 24 godziny, nawet jeśli ruch ustanie. Jeśli więc występują powtarzające się skoki ruchu, podziały są utrzymywane, a w razie potrzeby wprowadzane są kolejne. Te mechanizmy pomagają bazom danych Cloud Firestore automatycznie się skalować w przypadku zwiększonego obciążenia ruchem lub rozmiaru bazy danych. Istnieją jednak pewne ograniczenia, które opisujemy poniżej.

Podział miejsca na dane i obciążenia wymaga czasu, a zbyt szybkie zwiększanie natężenia ruchu może powodować duże opóźnienia lub błędy przekroczenia limitu czasu, powszechnie nazywane hotspotami, podczas gdy usługa się dostosowuje. Zalecamy rozłożenie operacji na cały zakres kluczy i zwiększanie natężenia ruchu w kolekcji w bazie danych do 500 operacji na sekundę. Po tym stopniowym zwiększaniu ruchu zwiększaj go o maksymalnie 50% co 5 minut. Ten proces jest nazywany regułą 500/50/5 i pozwala optymalnie skalować bazę danych pod kątem obciążenia.

Chociaż podziały są tworzone automatycznie wraz ze wzrostem obciążenia, Cloud Firestore może podzielić zakres kluczy tylko do momentu, gdy obsługuje pojedynczy dokument za pomocą dedykowanego zestawu zreplikowanych serwerów pamięci masowej. W rezultacie duża i utrzymująca się liczba równoczesnych operacji na jednym dokumencie może prowadzić do powstania punktu aktywnego w tym dokumencie. Jeśli w przypadku pojedynczego dokumentu występują stale wysokie opóźnienia, rozważ zmodyfikowanie 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.

Inny szczególny przypadek hotspottingu występuje, gdy jako identyfikator dokumentu w Cloud Firestore używany jest sekwencyjnie rosnący lub malejący klucz, a liczba operacji na sekundę jest bardzo duża. Tworzenie kolejnych podziałów nie pomoże, ponieważ wzrost ruchu po prostu przeniesie się do nowo utworzonego podziału. Ponieważ Cloud Firestore domyślnie automatycznie indeksuje wszystkie pola w dokumencie, takie ruchome punkty aktywne mogą być też tworzone w przestrzeni indeksu dla pola dokumentu, które zawiera sekwencyjnie rosnącą lub malejącą wartość, np. sygnaturę czasową.

Pamiętaj, że dzięki zastosowaniu opisanych powyżej praktyk usługa Cloud Firestore może skalować się, aby obsługiwać dowolnie duże zadania, bez konieczności dostosowywania konfiguracji.

Rozwiązywanie problemów

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

Co dalej?