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 lepiej poznać system. Jeśli dopiero zaczynasz korzystać z Cloud Firestore, 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 odbieranie 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.

Ten schemat przedstawia 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 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 również 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 podstawowych systemów pamięci masowej Cloud Firestore.

Odległość między fizyczną lokalizacją użytkownika a lokalizacją bazy danych Cloud Firestore ma wpływ na opóźnienie odczuwane przez użytkownika. Na przykład użytkownik z Indiek, którego aplikacja komunikuje się z bazą danych w regionie Google Cloud w Ameryce Północnej, może zauważyć wolniejsze działanie aplikacji i mniejsze jej tempo niż w przypadku bazy danych znajdującej się bliżej, np. w Indiach lub w 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 Cloud Firestore, aplikacja nadal będzie działać, korzystając z danych w 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 pakietów SDK na urządzenia mobilne.

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 zmian 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 detektorów 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 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 upoważniony, baza danych zwraca 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. Zmiany są wprowadzane w ścisłej kolejności w changelogu.
  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, skutecznie przeszukuje zapytania, aby znaleźć te, które pasują do przychodzącego dokumentu. 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 otrzymują 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 pamiętać, że wszystkie operacje odczytu wydawane przez pakiety SDK na urządzenia mobilne i pakiety SDK internetowe korzystają z powyższego modelu. Aby zapewnić spójność, najpierw wysyłają zapytanie o ankiety, a potem przechodzą w tryb słuchania. Dotyczy to też detektorów w czasie rzeczywistym, wywołań służących do pobierania dokumentu oraz zapytań jednorazowych. Wybieranie pojedynczych dokumentów i jednorazowe zapytania można traktować jako krótkotrwałych słuchaczy zrzutów, które mają podobne ograniczenia dotyczące 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 perspektywy klienta i pakietu SDK jest to przejrzyste rozwiązanie. Gdy dochodzi do podziału, aplikacja nie musi podejmować żadnych działań. Ten diagram pokazuje, jak skalować zapytania w czasie rzeczywistym:

Architektura rozgałęzienia historii zmian

Automatyczne skalowanie umożliwia zwiększanie ruchu zapisu bez ograniczeń, ale wraz ze wzrostem natężenia ruchu system może potrzebować więcej czasu na reakcję. Aby uniknąć tworzenia hotspotów zapisu, stosuj się do zaleceń 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 w bazie 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 się nie udadzą lub zostaną ograniczone, odczyt może się zatrzymać, czekając na spójne dane z changeloga. Jeśli tak się stanie w Twojej aplikacji, możesz zauważyć zarówno spowolnione operacje zapisu, jak i powiązane z nimi spowolnione czasy odpowiedzi na zapytania. Unikanie obszarów hotspotu 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 przesyłać małe dokumenty z dziesiątkami pól. Przetwarzanie większych dokumentów z setkami pól i dużymi danymi trwa dłużej.

Z kolei, aby skrócić czas oczekiwania, preferuj krótkie i szybkie operacje zatwierdzania oraz zapisu. Duże partie mogą zapewnić większą przepustowość z punktu widzenia autora, ale mogą też wydłużyć czas powiadomienia dla odbiorców zrzutów. Jest to często nieintuicyjne w porównaniu z użyciem 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.

Niektóre wzorce mogą jednak nadal prowadzić do nieoptymalnego działania w przypadku słuchaczy korzystających z migawek. Jeśli na przykład Twoja aplikacja przechowuje większość danych w jednej dużej kolekcji, odbiorca 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

Kolejnym ważnym elementem zapytań w czasie rzeczywistym jest upewnienie się, że zapytanie do pobierania danych jest szybkie i wydajne. 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.
  • Hotspoty powodują nieudane lub opóźnione zapisy do bazy danych.
  • Przejściowe restarty serwera mogą tymczasowo wpływać na słuchaczy.

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.

Otwarcie i utrzymanie słuchaczy przez jak najdłuższy czas jest często najbardziej opłacalnym sposobem tworzenia aplikacji korzystającej z funkcji Cloud Firestore. Gdy używasz interfejsu Cloud Firestore, płacisz za dokumenty zwrócone do aplikacji, a nie za utrzymywanie otwartego połączenia. Detektor zrzutu o długim czasie działania odczytuje tylko te dane, których potrzebuje do obsługi zapytania przez cały czas jego działania. Obejmuje to początkową operację odpytywania, a potem powiadomienia o rzeczywistych zmianach danych. Zapytania jednorazowe natomiast ponownie odczytują dane, które mogły się nie zmienić od czasu ostatniego uruchomienia zapytania przez aplikację.

W przypadku, gdy aplikacja musi zużywać dużą ilość danych, odbiorcy zrzutów ekranu 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