Interpretowanie zapytań w czasie rzeczywistym na dużą skalę

Przeczytaj ten dokument, aby dowiedzieć się, jak przeskalować aplikację bezserwerową w sposób przekraczający tysiące operacji na sekundę lub setki tysięcy równoczesnych użytkowników. Ten dokument zawiera zaawansowane tematy, które pomogą Ci dokładnie zrozumieć system. Jeśli dopiero zaczynasz korzystać z Cloud Firestore, przeczytaj krótki przewodnik.

Cloud Firestore oraz internetowe pakiety SDK Firebase stanowią zaawansowany model tworzenia bezserwerowych aplikacji, w których kod po stronie klienta bezpośrednio uzyskuje dostęp do bazy danych. Pakiety SDK umożliwiają klientom nasłuchiwanie aktualizacji danych w czasie rzeczywistym. Aktualizacje w czasie rzeczywistym pozwalają tworzyć elastyczne aplikacje, które nie wymagają infrastruktury serwera. Mimo że przygotowanie i uruchomienie jest bardzo łatwe, pomaga zrozumieć ograniczenia w systemach tworzących Cloud Firestore, aby bezserwerowa aplikacja była skalowana i działała dobrze przy wzroście ruchu.

W poniższych sekcjach znajdziesz porady dotyczące skalowania aplikacji.

Wybierz lokalizację bazy danych blisko użytkowników

Poniższy diagram przedstawia architekturę aplikacji działającej w czasie rzeczywistym:

Przykładowa architektura aplikacji w czasie rzeczywistym

Gdy aplikacja uruchomiona na urządzeniu użytkownika (mobilnym lub internetowym) nawiąże połączenie z Cloud Firestore, połączenie jest kierowane na serwer frontendu Cloud Firestore w tym samym regionie, w którym znajduje się Twoja baza danych. Jeśli na przykład baza danych znajduje się w regionie us-east1, połączenie trafia też do frontendu Cloud Firestore także w us-east1. Te połączenia są długotrwałe i pozostają otwarte, dopóki aplikacja ich nie zamknie. Frontend odczytuje dane z bazowych systemów pamięci Cloud Firestore.

Odległość między fizyczną lokalizacją użytkownika a lokalizacją bazy danych Cloud Firestore ma wpływ na opóźnienie występujące u użytkownika. Na przykład użytkownik w Indiach, którego aplikacja łączy się z bazą danych w regionie Google Cloud w Ameryce Północnej, może działać wolniej, a aplikacja będzie mniej wymagająca niż gdyby baza danych znajdowała się bliżej, np. w Indiach lub w innej części Azji.

Projektowanie z myślą o niezawodności

Te tematy zwiększają niezawodność aplikacji lub wpływają na nią:

Włącz tryb offline

Pakiety SDK Firebase zapewniają trwałość danych offline. Jeśli aplikacja na urządzeniu użytkownika nie może połączyć się z Cloud Firestore, nadal można jej używać dzięki pracy z danymi w lokalnej pamięci podręcznej. Zapewnia to dostęp do danych nawet w przypadku problemów z połączeniem internetowym lub utraty dostępu na kilka godzin lub dni. Więcej informacji o trybie offline znajdziesz w artykule Włączanie danych offline.

Informacje o automatycznych ponownych próbach

Pakiety SDK Firebase zajmują się ponawianiem operacji i przywracaniem zerwanych połączeń. Pomaga to obejść przejściowe błędy spowodowane ponownym uruchomieniem serwerów lub problemami z siecią między klientem a bazą danych.

Wybieranie lokalizacji w wielu regionach lub w wielu regionach

Wybierając lokalizację między regionami i wieloma regionami, trzeba liczyć się z kilkoma kompromisami. Główna różnica dotyczy sposobu replikacji danych. Napędza to gwarancje dostępności aplikacji. Instancja z wieloma regionami zapewnia większą niezawodność obsługi i trwałość danych, ale kompromis to koszty.

Omówienie systemu zapytań w czasie rzeczywistym

Zapytania w czasie rzeczywistym (nazywane też detektorami zrzutów) pozwalają aplikacji rejestrować zmiany w bazie danych i otrzymywać powiadomienia o małym czasie oczekiwania, gdy tylko zmienią się dane. Aplikacja może uzyskać ten sam wynik, okresowo odpytując bazę danych w poszukiwaniu aktualizacji, ale często działa wolniej, jest droższe i wymaga więcej kodu. Przykłady konfigurowania i używania zapytań w czasie rzeczywistym znajdziesz w artykule Otrzymywanie aktualizacji w czasie rzeczywistym. W kolejnych sekcjach dowiesz się, jak działają detektory zrzutów i opisują niektóre sprawdzone metody skalowania zapytań w czasie rzeczywistym przy zachowaniu wydajności.

Wyobraźmy sobie 2 użytkowników, którzy łączą się z Cloud Firestore za pomocą aplikacji do obsługi wiadomości utworzonej przy użyciu jednego z mobilnych pakietów SDK.

Klient A zapisuje w bazie danych, aby dodawać i aktualizować dokumenty w kolekcji o nazwie chatroom:

collection chatroom:
    document message1:
      from: 'Sparky'
      message: 'Welcome to Cloud Firestore!'

    document message2:
      from: 'Santa'
      message: 'Presents are coming'

Klient B nasłuchuje aktualizacji w tej samej kolekcji za pomocą detektora zrzutów. Klient B otrzymuje natychmiastowe powiadomienie za każdym razem, gdy ktoś utworzy nową wiadomość. Poniższy diagram przedstawia architekturę detektora zrzutów:

Architektura połączenia detektora zrzutów

Ta sekwencja zdarzeń ma miejsce, gdy Klient B połączy detektor zrzutów z bazą danych:

  1. Klient B otwiera połączenie z Cloud Firestore i rejestruje detektor, wywołując funkcję onSnapshot(collection("chatroom")) za pomocą pakietu SDK Firebase. Ten odbiornik może być aktywny przez wiele godzin.
  2. Frontend Cloud Firestore wysyła zapytanie do bazowego systemu pamięci masowej, aby wczytać zbiór danych. Wczytuje on cały zestaw wyników pasujących dokumentów. Nazywamy to zapytaniem ankietowym. Następnie system ocenia reguły zabezpieczeń Firebase bazy danych, aby sprawdzić, czy użytkownik ma dostęp do tych danych. Jeśli użytkownik ma odpowiednie uprawnienia, baza danych zwróci dane użytkownikowi.
  3. Zapytanie Klienta B przechodzi do trybu nasłuchiwania. Rejestrator rejestruje się w module obsługi subskrypcji i czeka na aktualizacje danych.
  4. Klient A wysyła teraz operację zapisu, aby zmodyfikować dokument.
  5. Baza danych zatwierdza zmianę w dokumencie do swojego systemu pamięci masowej.
  6. Transakcyjnie system zatwierdza tę samą aktualizację w wewnętrznym dzienniku zmian. Historia zmian określa rygorystyczną kolejność wprowadzania zmian.
  7. Z kolei historia zmian przesyła zaktualizowane dane do puli modułów obsługi subskrypcji.
  8. Uruchomi się mechanizm dopasowania odwrotnego zapytania, aby sprawdzić, czy zaktualizowany dokument pasuje do któregokolwiek z obecnie zarejestrowanych detektorów zrzutów. W tym przykładzie dokument pasuje do detektora zrzutów klienta B. Mechanizm dopasowywania odwrotnych zapytań można traktować jak normalne zapytanie do bazy danych, ale wykonywane w odwrotnej kolejności. Zamiast przeszukiwać dokumenty w celu znalezienia tych, które pasują do zapytania, funkcja sprawnie przeszukuje zapytania w celu znalezienia tych, które pasują do dokumentu przychodzącego. Po znalezieniu dopasowania system przekazuje dany dokument do detektorów zrzutów. Następnie system ocenia reguły zabezpieczeń Firebase bazy danych, aby upewnić się, że dane otrzymują tylko autoryzowani użytkownicy.
  9. System przekazuje aktualizację dokumentu do pakietu SDK na urządzeniu klienta B i uruchamia się wywołanie zwrotne onSnapshot. Jeśli włączona jest trwała lokalna, pakiet SDK zastosuje aktualizację również do lokalnej pamięci podręcznej.

Kluczowa część skalowalności Cloud Firestore zależy od rozpowszechnienia z dziennika zmian do modułów obsługi subskrypcji i serwerów frontendu. Rozpowszechnianie informacji pozwala na skuteczne rozpowszechnienie pojedynczej zmiany danych w celu obsługi milionów zapytań w czasie rzeczywistym i połączonych użytkowników. Uruchamiając wiele replik wszystkich tych komponentów w wielu strefach (lub w wielu regionach w przypadku wdrożenia obejmującego wiele regionów), Cloud Firestore zapewnia wysoką dostępność i skalowalność.

Warto zauważyć, że wszystkie operacje odczytu wykonywane z mobilnych i internetowych pakietów SDK są zgodne z powyższym modelem. Wykonują one zapytanie odpytujące, a następnie tryb nasłuchiwania, aby zachować gwarancje spójności. Dotyczy to również detektorów w czasie rzeczywistym, wywołań w celu pobrania dokumentu i zapytań jednorazowych. Pobieranie pojedynczych dokumentów i zapytania jednorazowe to na przykład krótkoterminowe detektory zrzutów, które mają podobne ograniczenia wydajności.

Stosowanie sprawdzonych metod skalowania zapytań w czasie rzeczywistym

Zastosuj poniższe sprawdzone metody, aby projektować skalowalne zapytania w czasie rzeczywistym.

Informacje o dużym ruchu zapisu w systemie

Z tej sekcji dowiesz się, jak system reaguje na rosnącą liczbę żądań zapisu.

Logi zmian Cloud Firestore, które generują zapytania w czasie rzeczywistym, automatycznie skalują się w poziomie wraz ze wzrostem ruchu zapisu. W miarę jak szybkość zapisu bazy danych rośnie powyżej tego, co może obsłużyć pojedynczy serwer, historia zmian jest dzielona na wiele serwerów i przetwarzanie zapytań zaczyna przetwarzać dane z wielu modułów obsługi subskrypcji, a nie z jednego. Z punktu widzenia klienta i pakietu SDK wszystko jest przejrzyste, a aplikacja nie musi podejmować żadnych działań. Poniższy diagram przedstawia skalę zapytań w czasie rzeczywistym:

Architektura zwielokrotnienia logu zmian

Automatyczne skalowanie pozwala bez ograniczeń zwiększyć ruch związany z zapisem, ale gdy ruch się zwiększa, reakcja systemu może trochę potrwać. Postępuj zgodnie z zaleceniami dotyczącymi reguły 5–5–5, aby uniknąć tworzenia obszaru roboczego zapisu. Key Visualizer to przydatne narzędzie do analizowania obszarów interaktywnych zapisu.

Wiele aplikacji cechuje się przewidywalnym, naturalnym wzrostem, do czego może służyć Cloud Firestore bez stosowania środków ostrożności. Zadania wsadowe, takie jak importowanie dużego zbioru danych, mogą jednak zbyt szybko przyspieszyć zapis. Podczas projektowania aplikacji zwróć uwagę na to, skąd pochodzi ruch zapisu.

Jak działają zapisy i odczyty

System zapytań w czasie rzeczywistym można traktować jak potok łączący operacje zapisu z czytelnikami. Za każdym razem, gdy dokument zostanie utworzony, zaktualizowany lub usunięty, zmiana zostanie przekazana z systemu pamięci do aktualnie zarejestrowanych detektorów. Struktura historii zmian w Cloud Firestore gwarantuje silną spójność, co oznacza, że aplikacja nigdy nie otrzymuje powiadomień o aktualizacjach, które są niezgodne z tymi, w których baza danych zatwierdziła zmiany danych. Ułatwia to tworzenie aplikacji dzięki eliminacji sytuacji skrajnych związanych ze spójnością danych.

Ten połączony potok oznacza, że operacja zapisu powodująca hotspoty lub rywalizacja o blokadę może negatywnie wpłynąć na operacje odczytu. Gdy operacje zapisu nie powiodą się lub dochodzi do ograniczenia, odczyt może się zawiesić w oczekiwaniu na spójne dane z dziennika zmian. Jeśli tak się stanie w aplikacji, możesz zauważyć zarówno powolne operacje zapisu, jak i skorelowany długi czas odpowiedzi na zapytania. Unikanie hotspotów to klucz do uniknięcia tego problemu.

Przechowuj dokumenty i zapisuj niewielkie operacje

Tworząc aplikacje z detektorami zrzutów, zwykle chcesz, aby użytkownicy szybko dowiedzieli się o zmianach w danych. Aby osiągnąć ten cel, staraj się unikać drobnych rzeczy. System może bardzo szybko przekazywać przez system niewielkie dokumenty zawierające dziesiątki pól. Przetwarzanie większych dokumentów z setkami pól i dużymi danymi trwa dłużej.

Podobnie faworyzuj krótkie i szybkie operacje zatwierdzania i zapisu, aby zminimalizować opóźnienie. Duże wsady mogą zapewnić większą przepustowość z punktu widzenia autora, ale mogą też wydłużyć czas powiadamiania detektorów zrzutów. Często jest to sprzeczne z intuicją w porównaniu z innymi systemami baz danych, w których można używać grupowania, aby zwiększyć wydajność.

Używaj wydajnych detektorów

W miarę jak rośnie szybkość zapisu bazy danych, Cloud Firestore dzieli przetwarzanie danych między wiele serwerów. Algorytm fragmentacji Cloud Firestore próbuje współlokować dane z tej samej kolekcji lub grupy kolekcji na tym samym serwerze historii zmian. System próbuje zmaksymalizować możliwą przepustowość zapisu, utrzymując jednocześnie jak najmniejszą liczbę serwerów zaangażowanych w przetwarzanie zapytania.

Jednak niektóre wzorce mogą nadal prowadzić do nieoptymalnego działania detektorów zrzutów. Jeśli na przykład Twoja aplikacja przechowuje większość danych w jednej dużej kolekcji, może być konieczne nawiązanie połączenia z wieloma serwerami w celu odebrania wszystkich potrzebnych danych. Dzieje się tak nawet po zastosowaniu filtra zapytania. Łączenie się z wieloma serwerami zwiększa ryzyko wolniejszych odpowiedzi.

Aby uniknąć tych wolniejszych odpowiedzi, zaprojektuj schemat i aplikację w taki sposób, aby system mógł obsługiwać detektory bez konieczności łączenia się z wieloma serwerami. Najlepiej podzielić dane na mniejsze zbiory z mniejszą szybkością zapisu.

Przypomina to analizowanie zapytań dotyczących wydajności w relacyjnej bazie danych, które wymagają pełnych skanowań tabel. W relacyjnej bazie danych zapytanie wymagające pełnego skanowania tabeli jest odpowiednikiem detektora zrzutu, który monitoruje kolekcję o wysokim prawdopodobieństwie rezygnacji. Może działać wolniej w porównaniu do zapytania, które baza danych może obsługiwać za pomocą bardziej szczegółowego indeksu. Zapytanie o bardziej szczegółowym indeksie jest jak detektor zrzutów, który ogląda pojedynczy dokument lub kolekcję, która rzadziej się zmienia. Aby jak najlepiej zrozumieć zachowanie i potrzeby danego przypadku użycia, musisz wczytać aplikację.

Szybkie odpytywanie

Inną kluczową częścią elastycznych zapytań w czasie rzeczywistym jest zapewnienie szybkiego i wydajnego wykonywania zapytań ankietowych służących do wczytywania danych. Przy pierwszym połączeniu z nowym detektorem zrzutów musi on wczytać cały zestaw wyników i wysłać go na urządzenie użytkownika. Powolne zapytania sprawiają, że aplikacja jest mniej elastyczna. Obejmuje to na przykład zapytania, które próbują odczytać wiele dokumentów, lub zapytania, które nie korzystają z odpowiednich indeksów.

W pewnych okolicznościach detektor może też wrócić ze stanu odtwarzania do trybu sondowania. Dzieje się to automatycznie i jest przezroczyste dla pakietów SDK i Twojej aplikacji. Te warunki mogą wywoływać stan odpytywania:

  • System równoważy historię zmian z powodu zmian obciążenia.
  • Hotspoty powodują błędy lub opóźnienia zapisu w bazie danych.
  • Tymczasowe ponowne uruchomienie serwera ma tymczasowo wpływ na detektory.

Jeśli zapytania odpytywania są wystarczająco szybkie, stan sondowania staje się jasny dla użytkowników aplikacji.

Wspieranie długoterminowych słuchaczy

Otwarcie i utrzymywanie detektorów na dłużej jest często najtańszym sposobem utworzenia aplikacji korzystającej z Cloud Firestore. W przypadku korzystania z Cloud Firestore płacisz za dokumenty zwracane do aplikacji, a nie za utrzymanie otwartego połączenia. Detektor długotrwałych zrzutów odczytuje tylko dane potrzebne do obsługi zapytania przez cały okres jego istnienia. Obejmuje to początkową operację odpytywania, po której następują powiadomienia o faktycznej zmianie danych. Zapytania jednorazowe umożliwiają natomiast ponowne odczytanie danych, które mogły się nie zmienić od czasu ostatniego wykonania zapytania przez aplikację.

W przypadkach, gdy aplikacja musi zużywać dużo danych, detektory zrzutów mogą być nieodpowiednie. Jeśli na przykład w Twoim przypadku użycia przekazujesz wiele dokumentów na sekundę przez połączenie przez dłuższy czas, lepiej wybrać zapytania jednorazowe uruchamiane z mniejszą częstotliwością.

Co dalej