Informacje o zapytaniach w czasie rzeczywistym na dużą skalę

Przeczytaj ten dokument, aby dowiedzieć się, jak skalować aplikację bezserwerową powyżej tysięcy operacji na sekundę lub setek tysięcy jednoczesnych użytkowników. Ten dokument zawiera zaawansowane tematy, które pomogą Ci dokładnie zrozumieć system. Jeśli dopiero zaczynasz korzystać z Cloud Firestore, zapoznaj się z krótkim przewodnikiem.

Cloud Firestore i pakiety SDK Firebase na urządzenia mobilne i do internetu zapewniają zaawansowany model tworzenia aplikacji bezserwerowych, w których kod po stronie klienta ma bezpośredni dostęp do bazy danych. Pakiety SDK umożliwiają klientom nasłuchiwanie aktualizacji danych w czasie rzeczywistym. Możesz używać aktualizacji w czasie rzeczywistym do tworzenia elastycznych aplikacji, które nie wymagają infrastruktury serwerowej. Chociaż bardzo łatwo jest coś uruchomić, warto zrozumieć ograniczenia systemów, które składają się na Cloud Firestore, aby aplikacja bezserwerowa skalowała się i działała dobrze, gdy ruch wzrośnie.

Wskazówki dotyczące skalowania aplikacji znajdziesz w sekcjach poniżej.

Wybierz lokalizację bazy danych blisko użytkowników

Na diagramie poniżej przedstawiono architekturę aplikacji działającej w czasie rzeczywistym:

Przykładowa architektura aplikacji działającej w czasie rzeczywistym

Gdy aplikacja działająca na urządzeniu użytkownika (mobilnym lub internetowym) nawiązuje połączenie z Cloud Firestore, jest ono kierowane do serwera frontendowego Cloud Firestore w tym samym regionie, w którym znajduje się baza danych. Jeśli na przykład baza danych znajduje się w regionie us-east1, połączenie jest też nawiązywane z frontendem Cloud Firestore w regionie us-east1. Te połączenia są długotrwałe i pozostają otwarte, dopóki nie zostaną jawnie zamknięte przez aplikację. Frontend odczytuje dane z bazowych systemów pamięci masowej Cloud Firestore.

Odległość między fizyczną lokalizacją użytkownika a lokalizacją bazy danych Cloud Firestore wpływa na opóźnienie, którego doświadcza użytkownik. Na przykład użytkownik w Indiach, którego aplikacja komunikuje się z bazą danych w regionie Google Cloud w Ameryce Północnej, może zauważyć, że aplikacja działa wolniej i mniej płynnie, 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 wpływają na niezawodność aplikacji:

Włączanie trybu offline

Pakiety SDK Firebase zapewniają trwałość danych w trybie offline. Jeśli aplikacja na urządzeniu użytkownika nie może połączyć się z Cloud Firestore, nadal można z niej korzystać, używając danych zapisanych lokalnie w pamięci podręcznej. Zapewnia to dostęp do danych nawet wtedy, gdy użytkownicy mają niestabilne połączenie z internetem lub całkowicie tracą dostęp na kilka godzin lub dni. Więcej informacji o trybie offline znajdziesz w artykule Włączanie danych offline.

Automatyczne ponowne próby

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

Wybór między lokalizacjami regionalnymi a lokalizacjami w wielu regionach

Wybór między lokalizacjami regionalnymi a lokalizacjami w wielu regionach wiąże się z kilkoma kompromisami. Główna różnica polega na sposobie replikacji danych. Od tego zależy gwarancja dostępności aplikacji. Instancja w wielu regionach zapewnia większą niezawodność i zwiększa trwałość danych, ale wiąże się z wyższymi kosztami.

Informacje o systemie zapytań w czasie rzeczywistym

Zapytania w czasie rzeczywistym, nazywane też detektorami zrzutów, umożliwiają aplikacji nasłuchiwanie zmian w bazie danych i otrzymywanie powiadomień o niskim opóźnieniu, gdy tylko dane ulegną zmianie. Aplikacja może uzyskać ten sam wynik, okresowo sprawdzając bazę danych pod kątem aktualizacji, ale często jest to wolniejsze, 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 sekcjach poniżej znajdziesz szczegółowe informacje o tym, jak działają detektory zrzutów, oraz opis niektórych sprawdzonych metod skalowania zapytań w czasie rzeczywistym przy zachowaniu wydajności.

Wyobraź 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 dane w bazie danych, aby dodać i zaktualizować 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, gdy ktoś utworzy nową wiadomość. Poniższy diagram przedstawia architekturę detektora zrzutów:

Architektura połączenia detektora zrzutu

Gdy klient B połączy detektor migawek z bazą danych, nastąpi taka sekwencja zdarzeń:

  1. Klient B otwiera połączenie z Cloud Firestore i rejestruje detektor, wywołując onSnapshot(collection("chatroom")) za pomocą pakietu SDK Firebase. Ten detektor może pozostać aktywny przez wiele godzin.
  2. Frontend Cloud Firestore wysyła zapytanie do bazowego systemu pamięci masowej, aby uruchomić zbiór danych. Wczytuje cały zbiór wyników pasujących dokumentów. Nazywamy to zapytaniem o stan. Następnie system sprawdza reguły zabezpieczeń Firebase bazy danych, aby zweryfikować, czy użytkownik ma dostęp do tych danych. Jeśli użytkownik jest autoryzowany, baza danych zwraca mu dane.
  3. Zapytanie klienta B przechodzi w tryb nasłuchiwania. Detektor 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ę dokumentu w systemie pamięci masowej.
  6. W ramach transakcji system zapisuje tę samą aktualizację w wewnętrznym dzienniku zmian. Lista zmian określa ścisłą kolejność zmian w miarę ich wprowadzania.
  7. Z kolei dziennik zmian przekazuje zaktualizowane dane do puli modułów obsługi subskrypcji.
  8. Uruchamiany jest mechanizm dopasowywania zapytań odwrotnych, aby sprawdzić, czy zaktualizowany dokument pasuje do obecnie zarejestrowanych detektorów zrzutów. W tym przykładzie dokument pasuje do detektora zrzutów klienta B. Jak sama nazwa wskazuje, dopasowywanie zapytań odwrotnych można traktować jak zwykłe zapytanie do bazy danych, ale wykonywane w odwrotny sposób. Zamiast przeszukiwać dokumenty w celu znalezienia tych, które pasują do zapytania, skutecznie przeszukuje zapytania, aby znaleźć te, które pasują do przychodzącego dokumentu. 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 tylko autoryzowani użytkownicy otrzymują dane.
  9. System przekazuje aktualizację dokumentu do pakietu SDK na urządzeniu klienta B i wywoływana jest funkcja wywołania zwrotnego onSnapshot. Jeśli włączona jest lokalna trwałość, pakiet SDK stosuje aktualizację również do lokalnej pamięci podręcznej.

Kluczowym elementem skalowalności Cloud Firestore jest rozsyłanie z dziennika zmian do modułów obsługi subskrypcji i serwerów frontendu. Dzięki temu pojedyncza zmiana danych może być skutecznie rozpowszechniana, aby obsługiwać miliony zapytań w czasie rzeczywistym i połączonych użytkowników. Dzięki uruchamianiu wielu replik wszystkich tych komponentów w wielu strefach (lub w wielu regionach w przypadku wdrożenia w wielu regionach) Cloud Firestore osiąga wysoką dostępność i skalowalność.

Warto zauważyć, że wszystkie operacje odczytu wykonywane z pakietów SDK na urządzenia mobilne i do aplikacji internetowych są zgodne z powyższym modelem. Wykonują zapytanie sondujące, a następnie przechodzą w tryb nasłuchiwania, aby zachować gwarancje spójności. Dotyczy to również detektorów w czasie rzeczywistym, wywołań pobierania dokumentu i zapytań jednorazowych. Pobieranie pojedynczych dokumentów i zapytania jednorazowe można traktować jako krótkotrwałe detektory zrzutów, które mają podobne ograniczenia dotyczące wydajności.

Zastosuj sprawdzone metody skalowania zapytań w czasie rzeczywistym

Aby projektować skalowalne zapytania w czasie rzeczywistym, stosuj te sprawdzone metody.

Zrozumienie dużego ruchu związanego z zapisem w systemie

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

Dzienniki zmian Cloud Firestore, które obsługują zapytania w czasie rzeczywistym, automatycznie skalują się w poziomie wraz ze wzrostem ruchu związanego z zapisem. Gdy szybkość zapisu w bazie danych przekracza możliwości pojedynczego serwera, dziennik zmian jest dzielony na wiele serwerów, a przetwarzanie zapytań zaczyna wykorzystywać dane z wielu modułów obsługi subskrypcji zamiast jednego. Z perspektywy klienta i pakietu SDK jest to całkowicie przejrzyste i nie wymaga żadnych działań ze strony aplikacji, gdy dochodzi do podziału. Poniższy diagram pokazuje, jak skalują się zapytania w czasie rzeczywistym:

Architektura dystrybucji historii zmian

Automatyczne skalowanie pozwala zwiększać ruch związany z zapisywaniem bez ograniczeń, ale gdy ruch się zwiększa, system może potrzebować trochę czasu na odpowiedź. Aby uniknąć tworzenia hotspotu zapisu, postępuj zgodnie z zaleceniami reguły 5-5-5. Key Visualizer to przydatne narzędzie do analizowania punktów zapisu.

Wiele aplikacji ma przewidywalny wzrost bezpłatny, który Cloud Firestore może obsłużyć bez środków ostrożności. Jednak zadania wsadowe, takie jak importowanie dużego zbioru danych, mogą zbyt szybko zwiększyć liczbę operacji zapisu. Podczas projektowania aplikacji pamiętaj, skąd pochodzi ruch związany z zapisywaniem.

Zrozumienie interakcji między zapisami i odczytami

System zapytań w czasie rzeczywistym można traktować jako potok łączący operacje zapisu z czytnikami. Za każdym razem, gdy dokument jest tworzony, aktualizowany lub usuwany, zmiana jest przekazywana z systemu pamięci masowej do obecnie zarejestrowanych detektorów. Struktura dziennika zmian Cloud Firestore gwarantuje silną spójność, co oznacza, że aplikacja nigdy nie otrzymuje powiadomień o aktualizacjach, które są niezgodne z kolejnością, w jakiej baza danych zatwierdziła zmiany danych. Upraszcza to tworzenie aplikacji, ponieważ eliminuje przypadki skrajne związane ze spójnością danych.

Ten połączony potok oznacza, że operacja zapisu powodująca powstawanie hotspotów lub rywalizację o blokadę może negatywnie wpływać na operacje odczytu. Gdy operacje zapisu nie powiodą się lub zostaną ograniczone, odczyt może zostać wstrzymany w oczekiwaniu na spójne dane z dziennika zmian. Jeśli tak się stanie w Twojej aplikacji, możesz zauważyć zarówno powolne operacje zapisu, jak i powiązane z nimi powolne czasy odpowiedzi na zapytania. Aby uniknąć tego problemu, należy unikać hotspotów.

Używaj małych dokumentów i operacji zapisu

Podczas tworzenia aplikacji ze detektorami zrzutów zwykle chcesz, aby użytkownicy szybko dowiadywali się o zmianach danych. Aby to osiągnąć, staraj się, aby były one niewielkie. System może bardzo szybko przetwarzać małe dokumenty z kilkudziesięcioma polami. Przetwarzanie większych dokumentów z setkami pól i dużymi ilościami danych trwa dłużej.

Podobnie, aby utrzymać krótki czas oczekiwania, preferuj krótkie i szybkie operacje zatwierdzania i zapisu. Duże partie mogą zapewnić większą przepustowość z perspektywy zapisu, ale mogą wydłużyć czas powiadomienia dla odbiorców migawek. Jest to często sprzeczne z intuicją w porównaniu z innymi systemami baz danych, w których można używać grupowania w celu zwiększenia wydajności.

Używanie wydajnych detektorów

Wraz ze wzrostem liczby zapisów w bazie danych Cloud Firestore rozdziela przetwarzanie danych między wiele serwerów. Algorytm fragmentacji Cloud Firestore próbuje umieścić dane z tej samej kolekcji lub grupy kolekcji na tym samym serwerze dziennika zmian. System stara się zmaksymalizować możliwą przepustowość zapisu, jednocześnie utrzymując liczbę serwerów zaangażowanych w przetwarzanie zapytania na jak najniższym poziomie.

Jednak niektóre wzorce mogą nadal prowadzić do nieoptymalnego działania detektorów zrzutów. Jeśli na przykład aplikacja przechowuje większość danych w jednej dużej kolekcji, detektor może potrzebować połączenia z wieloma serwerami, aby otrzymać wszystkie potrzebne dane. Dotyczy to nawet sytuacji, gdy zastosujesz filtr zapytania. Połączenie z wieloma serwerami zwiększa ryzyko wolniejszych odpowiedzi.

Aby uniknąć wolniejszych odpowiedzi, zaprojektuj schemat i aplikację tak, aby system mógł obsługiwać detektory bez konieczności korzystania z wielu różnych serwerów. Najlepiej podzielić dane na mniejsze kolekcje o mniejszej częstotliwości zapisu.

Jest to podobne do zapytań o wydajność w relacyjnej bazie danych, które wymagają pełnego skanowania tabeli. W relacyjnej bazie danych zapytanie, które wymaga skanowania całej tabeli, jest odpowiednikiem detektora zrzutów, który obserwuje kolekcję o dużej rotacji. Może ono działać wolniej niż zapytanie, które baza danych może obsłużyć za pomocą bardziej szczegółowego indeksu. Zapytanie z bardziej szczegółowym indeksem jest jak detektor zrzutów, który obserwuje pojedynczy dokument lub kolekcję, która zmienia się rzadziej. Aby lepiej zrozumieć zachowanie i potrzeby związane z Twoim przypadkiem użycia, przeprowadź test obciążenia aplikacji.

Szybkie zapytania dotyczące odpytywania

Innym kluczowym elementem responsywnych zapytań w czasie rzeczywistym jest zapewnienie, że zapytanie sondujące do uruchomienia danych jest szybkie i wydajne. Gdy nowy detektor migawek łączy się po raz pierwszy, musi wczytać cały zestaw wyników i wysłać go na urządzenie użytkownika. Powolne zapytania sprawiają, że aplikacja jest mniej responsywna. Obejmuje to na przykład zapytania, które próbują odczytać wiele dokumentów, lub zapytania, które nie używają odpowiednich indeksów.

W pewnych okolicznościach detektor może też powrócić ze stanu nasłuchiwania do stanu odpytywania. Dzieje się to automatycznie i jest niewidoczne dla pakietów SDK i aplikacji. Stan odpytywania mogą wywołać te warunki:

  • System ponownie równoważy dziennik zmian ze względu na zmiany obciążenia.
  • Hotspoty powodują nieudane lub opóźnione zapisy w bazie danych.
  • Przejściowe ponowne uruchomienia serwera tymczasowo wpływają na słuchaczy.

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

Preferuj detektory o długim czasie działania

Otwieranie i utrzymywanie detektorów aktywnych tak długo, jak to możliwe, jest często najbardziej opłacalnym sposobem tworzenia aplikacji korzystającej z Cloud Firestore. Gdy używasz Cloud Firestore, opłaty są naliczane za dokumenty zwracane do aplikacji, a nie za utrzymywanie otwartego połączenia. Długotrwały detektor zrzutów odczytuje tylko dane, których potrzebuje do obsługi zapytania przez cały okres jego istnienia. Obejmuje to początkową operację odpytywania, po której następują powiadomienia, gdy dane faktycznie się zmieniają. Z kolei zapytania jednorazowe ponownie odczytują dane, które mogły się nie zmienić od czasu ostatniego wykonania zapytania przez aplikację.

W sytuacjach, gdy aplikacja musi przetwarzać dużą ilość danych, detektory zrzutów mogą nie być odpowiednie. Jeśli na przykład Twój przypadek użycia wymaga przesyłania wielu dokumentów na sekundę przez połączenie przez dłuższy czas, lepszym rozwiązaniem mogą być zapytania jednorazowe, które są uruchamiane z mniejszą częstotliwością.

Co dalej?