Zapytania w czasie rzeczywistym na dużą skalę

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

Cloud Firestore oraz pakiety SDK Firebase do aplikacji mobilnych i internetowych zapewniają zaawansowany model tworzenia aplikacji bez serwera, 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. Aktualizacji w czasie rzeczywistym możesz używać do tworzenia responsywnych aplikacji, które nie wymagają infrastruktury serwerowej. Chociaż uruchomienie aplikacji jest bardzo proste, warto poznać ograniczenia systemów, które ją obsługują (Cloud Firestore), aby zapewnić jej skalowalność i wydajność przy zwiększonym natężeniu ruchu.

W następnych sekcjach znajdziesz wskazówki dotyczące skalowania aplikacji.

Wybierz lokalizację bazy danych w pobliżu użytkowników.

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

Przykładowa architektura aplikacji 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 Cloud Firestore w tym samym regionie, w którym znajduje się Twoja baza danych. Jeśli na przykład baza danych znajduje się w us-east1, połączenie jest też kierowane do frontendu Cloud Firestore, który też znajduje się 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 masowej Cloud Firestore.

Odległość między fizyczną lokalizacją użytkownika a lokalizacją bazy danych Cloud Firestore wpływa na czas oczekiwania. 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 działać mniej sprawnie niż w przypadku, gdy baza danych znajduje się bliżej, np. w Indiach lub innej części Azji.

Projektowanie z myślą o niezawodności

Poniższe tematy poprawiają niezawodność aplikacji lub wpływają na nią:

Włączanie trybu offline

Pakiety SDK Firebase zapewniają trwałość danych offline. Jeśli aplikacja na urządzeniu użytkownika nie może połączyć się z usługą Cloud Firestore, działa ona nadal z wykorzystaniem danych z lokalnej pamięci podręcznej. Dzięki temu użytkownicy będą mieli dostęp do danych nawet wtedy, gdy ich połączenie z internetem będzie niestabilne lub całkowicie utracą 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 dbają o ponowne próby wykonania operacji i nawiązanie przerwanych połączeń. Pomaga to w omijaniu błędów przejściowych spowodowanych przez restartowanie serwerów lub problemy z siecią między klientem a bazą danych.

Wybierz lokalizację regionalną lub wieloregionalną

Wybór między lokalizacją regionalną a lokalizacją obejmującą wiele regionów wiąże się z kilkoma kompromisami. Główna różnica polega na sposobie replikowania danych. To zapewnia dostępność gwarantowaną przez aplikację. Instancji w wielu regionach zapewnia większą niezawodność wyświetlania i zwiększa trwałość danych, ale kosztem jest koszt.

System zapytań w czasie rzeczywistym

Zapytania w czasie rzeczywistym, zwane też odbiorcami zrzutów, umożliwiają aplikacji monitorowanie zmian w bazie danych i otrzymywanie powiadomień o zmianach danych z minimalnym opóźnieniem. Aplikacja może uzyskać ten sam wynik, okresowo sprawdzając bazę danych pod kątem aktualizacji, ale jest to często 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 kolejnych sekcjach znajdziesz szczegółowe informacje o tym, jak działają odbiorcy podsumowania, oraz sprawdzone metody skalowania zapytań w czasie rzeczywistym przy zachowaniu wydajności.

Wyobraź sobie 2 użytkowników, którzy łączą się z usługą Cloud Firestore za pomocą aplikacji do obsługi wiadomości utworzonej za pomocą 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ą odbiorcy podsumowania. Klient B otrzymuje natychmiastowe powiadomienie, gdy ktoś utworzy nową wiadomość. Ten diagram przedstawia architekturę odbiorcy zrzutu:

Architektura połączenia odbiornika zrzutu

Gdy klient B łączy słuchacza zrzutu z bazą danych, następuje ta sekwencja zdarzeń:

  1. Klient B otwiera połączenie z Cloud Firestore i rejestruje listenera, wykonując wywołanie onSnapshot(collection("chatroom")) za pomocą pakietu SDK Firebase. Ten odsłuch może trwać wiele godzin.
  2. Frontend Cloud Firestore wysyła zapytania do podstawowego systemu magazynowania, aby zainicjować zbiór danych. Wczytuje on cały zestaw 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 ma odpowiednie uprawnienia, baza danych zwróci dane użytkownikowi.
  3. Zapytanie klienta B przechodzi wtedy w tryb słuchania. Listener rejestruje się w obiekcie obsługującym subskrypcję i czeka na aktualizacje danych.
  4. Klient A wysyła teraz operację zapisu, aby zmodyfikować dokument.
  5. Baza danych przekazuje zmianę dokumentu do systemu magazynowania.
  6. System zapisuje tę samą aktualizację w ramach transakcji w wewnętrznym pliku changelog. Historia zmian określa rygorystyczną kolejność wprowadzania zmian.
  7. Zmiany są następnie rozprowadzane do puli obsługi subskrypcji.
  8. Wykonywany jest odwrotny dopasowywanie zapytań, aby sprawdzić, czy zaktualizowany dokument pasuje do dowolnego z obecnie zarejestrowanych odbiorników migawek. W tym przykładzie dokument jest zgodny z odbiorcą zrzutu ekranu w kliencie B. Jak wskazuje nazwa, dopasowywacz zapytania odwrotnego można traktować jak zwykłe zapytanie do bazy danych, ale odwrotnie. 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 przekaże odpowiedni dokument odbiorcom zrzutów. Następnie system sprawdza reguły bezpieczeństwa Firebase bazy danych, aby upewnić się, że dane otrzymają tylko upoważnieni użytkownicy.
  9. System przekazuje aktualizację dokumentu do pakietu SDK na urządzeniu klienta B, a następnie wywołuje funkcję onSnapshot. Jeśli trwałość lokalna jest włączona, pakiet SDK stosuje aktualizację również do lokalnej pamięci podręcznej.

Kluczowym elementem skalowalności Cloud Firestore jest rozgałęzienie od listy zmian do modułów obsługi subskrypcji i serwerów interfejsu. Rozgałęzienie umożliwia sprawne rozpowszechnianie jednej zmiany danych, aby obsługiwać miliony połączonych użytkowników i zapytań w czasie rzeczywistym. Dzięki uruchamianiu wielu replik wszystkich tych komponentów w wielu strefach (lub w wielu regionach w przypadku wdrożenia wielostrefowego) Cloud Firestore osiąga 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. Aby zapewnić spójność, najpierw wykonują zapytanie o ankiety, a potem przechodzą w tryb słuchania. Dotyczy to też odbiorców w czasie rzeczywistym, wywołań służących do pobierania dokumentu oraz 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.

Stosuj sprawdzone metody skalowania zapytań w czasie rzeczywistym

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

Wysoki ruch zapisu w systemie

Z tej sekcji dowiesz się, jak system reaguje na rosnącą liczbę próśb o zapisywanie.

Zmiany w Cloud Firestore, które powodują zapytania w czasie rzeczywistym, automatycznie zwiększają się w poziomie wraz ze wzrostem ruchu zapisującego. Gdy szybkość zapisu bazy danych przekroczy możliwości pojedynczego serwera, plik zmian zostanie podzielony na kilka serwerów, a przetwarzanie zapytań zacznie korzystać z danych z wielu modułów obsługi subskrypcji zamiast z jednego. Z punktu widzenia klienta i pakietu SDK wszystko jest przejrzyste, a aplikacja nie musi podejmować żadnych działań. Ten diagram pokazuje, jak skalować zapytania w czasie rzeczywistym:

Architektura zwielokrotnienia logu zmian

Automatyczne skalowanie pozwala zwiększyć ruch zapisu bez ograniczeń, ale gdy ruch się zwiększa, reakcja systemu może trochę potrwać. Aby uniknąć tworzenia hotspotów zapisu, postępuj zgodnie z zaleceniami reguły 5-5-5. Key Visualizer to przydatne narzędzie do analizowania miejsc, w których najczęściej występują błędy.

Wiele aplikacji ma przewidywalny wzrost organiczny, który Cloud Firestore może uwzględnić bez konieczności podejmowania środków ostrożności. Zadania wsadowe, takie jak importowanie dużych zbiorów danych, mogą jednak powodować zbyt szybki wzrost liczby zapisów. Podczas projektowania aplikacji pamiętaj, skąd pochodzi ruch zapisu.

Jak działają zapisy i odczyty

System zapytań w czasie rzeczywistym możesz traktować jako potok łączący operacje zapisu z czytnikami. Za każdym razem, gdy dokument jest tworzony, aktualizowany lub usuwany, zmiana jest rozpowszechniana z systemu magazynowania do zarejestrowanych w danej chwili odbiorców. Struktura pliku changelog w wersji Cloud Firestore gwarantuje spójność, co oznacza, że Twoja aplikacja nigdy nie otrzyma powiadomień o aktualizacjach, które są nieprawidłowe w stosunku do momentu zatwierdzenia zmian danych przez bazę danych. Upraszcza to tworzenie aplikacji, ponieważ eliminuje przypadki szczególne związane z spójnością danych.

Połączony potok oznacza, że operacja zapisu powodująca problemy z wydajnością lub konflikty blokad może negatywnie wpływać 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 Twojej aplikacji, możesz zauważyć zarówno wolne operacje zapisu, jak i powiązane z nimi długie czasy odpowiedzi na zapytania. Unikanie obszarów hotspotów to klucz do unikania tego problemu.

Utrzymywanie małych dokumentów i operacji zapisu

Podczas tworzenia aplikacji z odbiornikami zrzutów ekranu zwykle zależy Ci na tym, aby użytkownicy szybko dowiadywali się o zmianach danych. Aby to osiągnąć, staraj się, aby elementy były małe. 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. Jest to często nieintuicyjne w porównaniu z używaniem innych systemów baz danych, w których do poprawy wydajności można użyć grupowania.

Korzystanie z skutecznych odbiorców

Wraz ze wzrostem szybkości zapisu w bazie danych Cloud Firestore rozdziela przetwarzanie danych na wiele serwerów. Algorytm partycjonowania Cloud Firestore próbuje umieszczać dane z tej samej kolekcji lub grupy kolekcji na tym samym serwerze zmian. System stara się zmaksymalizować możliwą przepustowość zapisu, utrzymując przy tym 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, odbiornik może potrzebować połączenia z wielu serwerami, aby otrzymać wszystkie potrzebne dane. Dotyczy to nawet sytuacji, gdy zastosujesz filtr zapytania. Łączenie się z wiele serwerami zwiększa ryzyko wolniejszej odpowiedzi.

Aby uniknąć wolniejszych odpowiedzi, zaprojektuj schemat i aplikację tak, aby system mógł obsługiwać słuchaczy bez korzystania z wielu różnych serwerów. Najlepiej podzielić dane na mniejsze zbiory o mniejszych szybkościach zapisu.

Jest to podobne do zastanowienia się nad zapytaniami dotyczącymi wydajności w bazie danych relacyjnej, które wymagają pełnego skanowania tabeli. W relacyjnej bazie danych zapytanie, które wymaga pełnego skanowania tabeli, jest odpowiednikiem podsłuchiwania zrzutu, które obserwuje kolekcję o wysokiej rotacji. Może ono działać wolniej w porównaniu z zapytaniem, które baza danych może obsłużyć za pomocą bardziej szczegółowego indeksu. Zapytanie z bardziej szczegółowym indeksem działa jak detektor zrzutu, który obserwuje pojedynczy dokument lub kolekcję, która zmienia się rzadziej. Aby lepiej zrozumieć zachowanie i potrzeby związane z przypadkiem użycia, przeprowadź test wczytywania aplikacji.

Szybkie zapytania dotyczące odpytywania

Inną kluczową częścią elastycznych zapytań w czasie rzeczywistym jest zapewnienie szybkiej i skuteczności zapytań ankietowych służących do wczytywania danych. Gdy nowy słuchacz zrzutu łączy się po raz pierwszy, musi załadować cały zbiór wyników i przesłać go na urządzenie użytkownika. Powolne zapytania powodują, że aplikacja działa wolniej. Dotyczy to na przykład zapytań, które próbują odczytać wiele dokumentów, lub zapytań, które nie korzystają z odpowiednich indeksów.

W niektórych okolicznościach słuchacz może też przejść ze stanu słuchania do stanu sondowania. Dzieje się to automatycznie i nie wymaga interwencji ze strony SDK ani aplikacji. Stan odpytywania może być wywołany przez te warunki:

  • System ponownie równoważy zmiany ze względu na zmiany obciążenia.
  • Miejsca o wysokiej aktywności powodują niepowodzenie lub opóźnienie zapisu do bazy danych.
  • Tymczasowe ponowne uruchomienie serwera ma tymczasowo wpływ na detektory.

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

Preferuj słuchaczy o długim czasie trwania.

Otwieranie i utrzymywanie słuchaczy przez jak najdłuższy czas jest często najbardziej opłacalnym sposobem tworzenia aplikacji korzystającej z funkcji Cloud Firestore. W przypadku korzystania z Cloud Firestore naliczane są opłaty za dokumenty zwrócone 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 przypadku, gdy aplikacja musi zużywać dużą ilość danych, odbiorcy migawek mogą nie być odpowiedni. Jeśli na przykład w Twoim przypadku przez dłuższy czas przesyłanych jest wiele dokumentów na sekundę, lepiej jest wybrać jednorazowe zapytania, które są wykonywane z mniejszym natężeniem.

Co dalej