Odczyty i zapisy na dużą skalę

Przeczytaj ten dokument, aby podejmować świadome decyzje dotyczące 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 z Firebase i Google Cloud. Rozpoczęcie pracy z Cloud Firestore i tworzenie bogatych, wydajnych aplikacji jest bardzo proste.

Aby mieć pewność, że Twoje aplikacje będą działać prawidłowo, gdy rozmiar bazy danych i ruch będą się zwiększać, warto poznać mechanizm odczytu i zapisu na Cloud Firestore backendzie. Musisz też zrozumieć, jak procesy odczytu i zapisu współdziałają z poziomem pamięci masowej oraz jakie ograniczenia mogą wpływać na wydajność.

Zanim zaczniesz projektować aplikację, zapoznaj się ze sprawdzonymi metodami opisanymi w następnych sekcjach.

Omówienie ogólnych elementów

Na diagramie poniżej widać ogólne komponenty, które są wykorzystywane w żądaniu do interfejsu API Cloud Firestore.

Komponenty wysokiego poziomu

Cloud Firestore Pakiety SDK i biblioteki klienta

Cloud Firestore obsługuje pakiety SDK i biblioteki klienta na różne platformy. Aplikacja może wykonywać bezpośrednie wywołania HTTP i RPC 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 przychodzące żądania i przekierowuje je do odpowiedniej usługi Google (w tym kontekście „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 wykonuje kontrole żądania interfejsu API, które obejmują uwierzytelnianie, autoryzację, kontrole dotyczące limitów i reguł bezpieczeństwa, a także zarządza transakcjami. Ta usługa Cloud Firestore zawiera klienta usługi przechowywania, który współpracuje z warstwą przechowywania w celu odczytu i zapisu danych.

Cloud Firestore warstwa pamięci

Warstwa pamięci Cloud Firestore odpowiada za przechowywanie danych i metadanych oraz powiązanych funkcji bazy danych udostępnianych przez Cloud Firestore. W następnych sekcjach opisujemy sposób organizacji danych na warstwie pamięci Cloud Firestore oraz skalowanie systemu. Poznanie sposobu organizacji danych może pomóc Ci zaprojektować skalowalny model danych i lepiej zrozumieć sprawdzone metody w Cloud Firestore.

Kluczowe zakresy i podziały

Cloud Firestore to baza danych NoSQL zorientowana na dokumenty. Dane są przechowywane w dokumentach, które są uporządkowane w hierarchii kolekcji. Hierarchia kolekcji i identyfikator dokumentu są przekształcane w pojedynczy klucz dla każdego dokumentu. Dokumenty są logicznie przechowywane i uporządkowane alfabetycznie według tego klucza. Używamy terminu zakres kluczy, aby określić ciągły zakres kluczy uporządkowanych alfabetycznie.

Typowa baza danych Cloud Firestore jest zbyt duża, aby zmieścić się na jednym komputerze fizycznym. Są też sytuacje, w których obciążenie związane z danymi jest zbyt duże, aby pojedyncza maszyna mogła je obsłużyć. Aby obsługiwać duże obciążenia, Cloud Firestore dzieli dane na osobne części, które można przechowywać i przekazywać z wielu maszyn lub serwerów pamięci masowej. 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. Kopie danych mają repliki w różnych strefach, aby były dostępne nawet wtedy, gdy dana strefa stanie się niedostępna. Konsekwentna replikacja na różne kopie podziału jest zarządzana przez algorytm Paxos służący do osiągania konsensusu. 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 zawsze odczyt najnowszej wersji danych z Cloud Firestore.

Ogólny rezultat to skalowalny i wysoko dostępny system, który zapewnia krótki czas oczekiwania zarówno na odczyt, jak i na zapis, niezależnie od obciążenia i skali.

Układ danych

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

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

Na schemacie poniżej widać, jak mogą wyglądać tabele bazy danych Cloud Firestore po podziale. Podziały są replikowane 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 konkretna lokalizacja geograficzna, np. us-west1. Poszczególne części bazy danych Cloud Firestore mają kopie w różnych strefach w wybranym regionie, jak wyjaśniono wcześniej.

Lokalizacja obejmująca wiele regionów składa się z określonego zestawu regionów, w których są przechowywane repliki bazy danych. W przypadku wdrożenia Cloud Firestore w wielu regionach dwa regiony mają pełne repliki wszystkich danych w bazie danych. Trzeci region ma replikę świadka, która nie przechowuje pełnego zestawu danych, ale uczestniczy w replikacji. Dzięki replikowaniu danych między wieloma regionami można je zapisywać i czytać nawet po utracie całego regionu.

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

Jeden region a wiele regionów

Zrozumienie życia w Cloud Firestore

Klient Cloud Firestore może zapisywać dane, tworząc, aktualizując lub usuwając pojedynczy dokument. Zapisywanie danych w pojedynczym dokumencie wymaga zaktualizowania zarówno dokumentu, jak i powiązanych z nim wpisów indeksu w warstwie pamięci. Cloud Firestore obsługuje też operacje atomowe obejmujące wiele odczytów lub zapisów w co najmniej 1 dokumencie.

W przypadku wszystkich rodzajów zapisów usługa Cloud Firestore zapewnia właściwości ACID (atomowość, spójność, izolacja i trwałość) baz danych relacyjnych. Cloud Firestore zapewnia też możliwość sekwencyjnego przetwarzania, co oznacza, że wszystkie transakcje wyglądają tak, jakby były wykonywane w kolejności.

Najważniejsze kroki w transakcji zapisu

Gdy klient Cloud Firestore wykonuje operację zapisu lub zatwierdza transakcję za pomocą dowolnej z wymienionych wcześniej metod, wewnętrznie jest to wykonywane jako transakcja bazy danych odczytu/zapisu na poziomie warstwy pamięci masowej. Transakcja umożliwia Cloud Firestore udostępnienie wymienionych wcześniej właściwości ACID.

Pierwszym krokiem transakcji jest odczytanie przez Cloud Firestore istniejącego dokumentu i określenie zmian, które należy wprowadzić w danych w tabeli Documents.

Obejmuje to również wprowadzenie niezbędnych zmian w tabeli Indexes w ten sposób:

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

Aby obliczyć wspomniane wcześniej mutacje, Cloud Firestore odczytuje konfigurację indeksowania projektu. Konfiguracja indeksowania przechowuje informacje o indeksach projektu. Cloud Firestore używa 2 typów indeksów: pojedynczego pola i złożonego. 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 ramach transakcji, a następnie zatwierdza.

Transakcja zapisu w warstwie pamięci

Jak już wspomnieliśmy, zapis w Cloud Firestore wymaga transakcji odczytu-zapisu w warstwie składowania danych. W zależności od układu danych zapis może obejmować co najmniej 1 podział, jak widać na układzie danych.

Na poniższym diagramie baza danych Cloud Firestore ma 8 podzielonych części (oznaczonych numerami 1–8), które są hostowane na 3 różnych serwerach pamięci masowej w jednej strefie. Każda część jest replikowana w 3 lub większej liczbie stref. 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ż bazę danych Cloud Firestore, która zawiera zbiór Restaurants w taki sposób:

Kolekcja restauracji

Klient Cloud Firestore prosi o wprowadzenie tej zmiany w dokumencie w zbiorze Restaurant, aktualizując wartość pola priceCategory.

Zmiana dokumentu w kolekcji

Poniżej opisano ogólnie, co się dzieje podczas zapisu:

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

Klient usługi pamięci masowej w usłudze Cloud Firestore wyszukuje podziały, które są właścicielami kluczy wierszy, które mają zostać zmienione. Rozważmy przypadek, w którym grupa 3 obsługuje grupę 1, a grupa 6 – grupy 2–5. Jest to transakcja rozproszona, w której wszystkie te podziały są uczestnikami. Podziały uczestników mogą też obejmować dowolny inny podział, z którego dane zostały odczytane wcześniej w ramach transakcji odczytu i zapisu.

Poniżej opisujemy, co dzieje się podczas zatwierdzania:

  1. Klient pamięci masowej wysyła polecenie commit. Commit zawiera mutacje M1–M5.
  2. Splity 3 i 6 są uczestnikami tej transakcji. Jeden z uczestników jest wybierany jako koordynator, np. Grupa 3. Zadaniem koordynatora jest zapewnienie, aby transakcja została zaakceptowana lub przerwana w sposób atomowy dla 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 uruchamia algorytm Paxos na swoich replikach.
    • Lider wykonuje algorytm Paxos na replikach. Kwarum jest osiągane, gdy większość replik odpowiada liderowi odpowiedzią ok to commit.
    • Każdy uczestnik informuje koordynatora, gdy jest gotowy (pierwszy etap 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ą gotowi, przekazuje wynik transakcji accept wszystkim uczestnikom (drugi etap dwufazowego zatwierdzania). W tej fazie każdy uczestnik zapisuje decyzję o zatwierdzeniu w stabilnym miejscu przechowywania, a transakcja zostaje zatwierdzona.
  5. Koordynator odpowiada klientowi usługi przechowywania w Cloud Firestore, że transakcja została zatwierdzona. Równocześnie koordynator i wszyscy uczestnicy wprowadzają zmiany w danych.

Cykl życia zatwierdzenia

Gdy baza danych Cloud Firestore jest mała, może się zdarzyć, że pojedynczy podział będzie posiadać wszystkie klucze w mutacjach M1–M5. W takim przypadku w transakcji bierze udział tylko jeden uczestnik, a wymieniona wcześniej dwufazowa operacja zatwierdzania nie jest wymagana, co przyspiesza zapisywanie.

Zapisywanie w wielu regionach

W przypadku wdrożenia obejmującego wiele regionów rozmieszczenie 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 wymaga więcej czasu. Dlatego opóźnienie podstawowe w przypadku operacji Cloud Firestore jest nieco większe w porównaniu z wdrożeniami w jednym regionie.

Repliki są konfigurowane w taki sposób, aby w przypadku podziału zawsze była wybierana replika wiodącego regionu. Główny region to region, z którego na serwer Cloud Firestore trafia ruch. Ta decyzja o prowadzeniu zmniejsza opóźnienie w komunikacji w obie strony między klientem usługi przechowywania w Cloud Firestore a liderem repliki (lub koordynatorem w przypadku transakcji z wielokrotnie podzielonymi danymi).

Każda operacja zapisu w komponencie Cloud Firestore wiąże się też z pewną interakcją z silnikiem w czasie rzeczywistym w komponencie Cloud Firestore. Więcej informacji o zapytaniach w czasie rzeczywistym znajdziesz w artykule Omówienie zapytań w czasie rzeczywistym na dużą skalę.

Dowiedz się, jak długo trwa wyświetlanie treści Cloud Firestore

Ta sekcja zawiera szczegółowe informacje o samodzielnych odczytach w trybie innym niż w czasie rzeczywistym 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 Indeksy,
  2. punktowe wyszukiwania w tabeli Documents (Dokumenty) na podstawie wyniku wcześniejszego skanowania;
W zapytaniach Cloud Firestore mogą występować zapytania, które wymagają mniej lub więcej przetwarzania (np. zapytania z IN).

Odczyty danych z poziomu pamięci są wykonywane wewnętrznie za pomocą transakcji bazy danych, aby zapewnić spójne odczyty. Jednak w przeciwieństwie do transakcji używanych do zapisu te transakcje nie blokują dostępu. Zamiast tego wybierają sygnaturę czasową, a następnie wykonują wszystkie odczyty w tym momencie. Ponieważ nie blokują zasobów, nie blokują też równoczesnych transakcji odczytu-zapisu. Aby wykonać tę transakcję, klient miejsca na dane w Cloud Firestore określa ograniczenie sygnatury czasowej, które informuje warstwę miejsca na dane, jak wybrać sygnaturę czasową odczytu. Typ sygnatury czasowej wybrany przez klienta usługi magazynu w Cloud Firestore jest określany przez opcje odczytu w żądaniu odczytu.

Transakcja odczytu w warstwie pamięci

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

Silne odczyty

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

Single Split read

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

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

  • Żądanie odczytu jest wysyłane do głównej repliki (strefa A).
    • Ponieważ lider jest zawsze na bieżąco, odczyt może być przeprowadzony bezpośrednio.
  • Żądanie odczytu jest wysyłane do repliki niebędącej liderem (np. strefy B).
    • Rozgałęzienie 3 może na podstawie swojego stanu wewnętrznego wiedzieć, że ma wystarczającą ilość informacji do obsłużenia odczytu, i to robi.
    • Rozdział 3 nie jest pewien, czy ma najnowsze dane. Wysyła wiadomość do lidera, aby poprosić o sygnaturę czasową ostatniej transakcji, której należy użyć do wyświetlenia odczytu. Po zastosowaniu tej transakcji można kontynuować odczyt.

Cloud Firestore zwraca odpowiedź klientowi.

Odczyt z wieloma podziałami

W sytuacji, gdy odczyty muszą być wykonywane z wielu podziałów, ten sam mechanizm działa we wszystkich podziale. Gdy zwrócone zostaną dane ze wszystkich podziałów, klient usługi przechowywania danych w Cloud Firestore połączy wyniki. Cloud Firestore przekazuje te dane klientowi.

Nieaktualne odczyty

Silne odczyty są domyślnym trybem w Cloud Firestore. Jednak wiąże się to z potencjalnie dłuższym opóźnieniem ze względu na konieczność komunikacji z liderem. Często aplikacja Cloud Firestore nie musi odczytywać najnowszej wersji danych, a jej funkcje działają dobrze z danymi, które mogą być nieaktualne o kilka sekund.

W takim przypadku klient może zdecydować się na otrzymywanie nieaktualnych odczytów, korzystając z opcji odczytu read_time. W takim przypadku odczyty są wykonywane tak, jak dane były w czasie read_time, a najbliższa replika prawdopodobnie już zweryfikowała, że ma dane w określonym czasie read_time. Aby uzyskać zauważalnie lepszą wydajność, ustaw wartość 15 sekund. Nawet w przypadku nieaktualnych odczytów wiersze są ze sobą spójne.

Unikaj obszarów o wysokiej aktywności

PodzbioryCloud Firestore są automatycznie dzielone na mniejsze części, aby w razie potrzeby lub gdy przestrzeń klucza się zwiększy, rozprosić zadanie obsługi ruchu na więcej serwerów pamięci masowej. Podziały utworzone na potrzeby obsługi nadmiaru ruchu są zachowywane przez około 24 godziny, nawet jeśli ruch się skończy. Jeśli więc występują powtarzające się wzrosty ruchu, podziały są utrzymywane i w razie potrzeby wprowadzane są kolejne podziały. Te mechanizmy pomagają automatycznie skalować bazy danych Cloud Firestore w przypadku zwiększania się natężenia ruchu lub rozmiaru bazy danych. Pamiętaj jednak, że istnieją pewne ograniczenia, o których przeczytasz poniżej.

Podział pamięci i ładowania zajmuje czas, a zbyt szybkie zwiększanie natężenia ruchu może spowodować duże opóźnienia lub przekroczenie limitu czasu, czyli problemy z obciążeniem, które występują, gdy usługa się dostosowuje. Sprawdzoną metodą jest rozprowadzanie operacji na kluczowym zakresie, przy jednoczesnym zwiększaniu ruchu w zbiorze w bazie danych o 500 operacji na sekundę. Po tym stopniowym zwiększaniu zwiększaj ruch o maksymalnie 50% co 5 minut. Ten proces nazywa się regułą 500/50/5 i polega na optymalnym dostosowaniu skali bazy danych do Twojego zbioru zadań.

Chociaż podziały są tworzone automatycznie wraz ze wzrostem obciążenia, Cloud Firestore może podzielić zakres kluczy tylko do momentu, gdy będzie obsługiwać jeden dokument za pomocą dedykowanego zestawu replikowanych serwerów pamięci masowej. W efekcie duża i ciągła liczba operacji wykonywanych jednocześnie na jednym dokumencie może spowodować powstanie w nim hotspotu. Jeśli w przypadku pojedynczego dokumentu występują długotrwałe opóźnienia, rozważ zmodyfikowanie modelu danych w celu podziału lub replikowania danych na wiele dokumentów.

Błędy rywalizacji występują, gdy wiele operacji próbuje odczytać lub zapisać ten sam dokument w tym samym czasie.

Innym szczególnym przypadkiem hotspottingu jest sytuacja, gdy w funcji Cloud Firestore jako identyfikatora dokumentu używa się klucza rosnącego lub malejącego i występuje znacznie wysoka liczba operacji na sekundę. Utworzenie większej liczby 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ć tworzone w przestrzeni indeksu dla pola dokumentu, które zawiera wartość rosnącą lub malejącą, np. sygnaturę czasową.

Pamiętaj, że dzięki stosowaniu opisanych wyżej metod usługa Cloud Firestore może się skalować, aby obsługiwać dowolnie duże obciążenia bez konieczności dostosowywania konfiguracji.

Rozwiązywanie problemów

Cloud Firestore udostępnia narzędzie diagnostyczne Key Visualizer, które służy do analizowania wzorów użytkowania i rozwiązywania problemów z hotspotami.

Co dalej