Optymalizowanie wydajności zapytań

Aby rozwiązać problemy z powolnymi zapytaniami, użyj funkcji Query Explain, aby uzyskać plan wykonywania zapytania i profil wykonania w czasie działania. W sekcji poniżej znajdziesz opis czynności, które możesz wykonać, aby zoptymalizować wydajność zapytań w zależności od profilu wykonania:

Ograniczanie liczby wyników

Użyj pola zwróconych rekordów w drzewie wykonania, aby sprawdzić, czy zapytanie zwraca wiele dokumentów. Rozważ ograniczenie liczby zwracanych dokumentów za pomocą etapu limit(...). Zmniejsza to rozmiar serializowanych bajtów wyników zwracanych klientom przez sieć. W przypadku, gdy węzeł Limit jest poprzedzony węzłem MajorSort, silnik zapytań może połączyć węzły LimitMajorSort oraz zastąpić pełną materializację i sortowanie w pamięci sortowaniem TopN, co zmniejsza wymagania dotyczące pamięci dla zapytania.

Ograniczanie rozmiaru dokumentu wynikowego

Rozważ ograniczenie rozmiaru zwracanego dokumentu za pomocą parametru select(...), aby zwracać tylko wymagane pola, lub parametru remove_fields(...), aby odrzucać zbyt duże pola. Pomaga to zmniejszyć koszty obliczeniowe i koszty pamięci związane z przetwarzaniem wyników pośrednich oraz rozmiar serializowanych wyników w bajtach, gdy są one zwracane do klientów przez sieć. W przypadku, gdy wszystkie pola, do których odwołuje się zapytanie, są objęte zwykłym indeksem, umożliwia to również pełne pokrycie zapytania przez skanowanie indeksu, co pozwala uniknąć pobierania dokumentów z pamięci podstawowej.

Korzystanie z indeksów

Aby skonfigurować i zoptymalizować indeksy, postępuj zgodnie z tymi instrukcjami.

Sprawdzanie, czy zapytanie używa indeksu

Aby sprawdzić, czy zapytanie korzysta z indeksu, możesz przejrzeć węzły liści w drzewie wykonania. Jeśli węzeł liścia drzewa wykonania jest węzłem TableScan, oznacza to, że zapytanie nie używa indeksu i skanuje dokumenty z pamięci podstawowej. Jeśli używany jest indeks, węzeł liścia drzewa wykonania będzie wyświetlać identyfikator indeksu i pola indeksu.

Wybieranie lepszego indeksu

Indeks jest przydatny w przypadku zapytania, jeśli może zmniejszyć liczbę dokumentów, które silnik zapytań musi pobrać z pamięci podstawowej, lub jeśli kolejność pól może spełnić wymagania zapytania dotyczące sortowania.

Jeśli zapytanie korzysta z indeksu, ale silnik zapytań nadal pobiera i odrzuca wiele dokumentów (co można stwierdzić na podstawie węzła skanowania, który zwraca wiele rekordów, a następnie węzła filtra, który zwraca niewiele rekordów), oznacza to, że predykat zapytania spełniony przy użyciu indeksu nie jest selektywny. Aby utworzyć bardziej odpowiedni indeks, zapoznaj się z artykułem Tworzenie indeksów.

Jeśli zapytanie korzysta z indeksu, ale mechanizm zapytań nadal wykonuje w pamięci ponowne porządkowanie zbioru wyników, co jest wskazywane przez węzeł MajorSort w drzewie wykonania zapytania, oznacza to, że użyty indeks nie może spełnić wymagania sortowania zapytania. Aby utworzyć bardziej odpowiedni indeks, zapoznaj się z następną sekcją.

Tworzenie indeksów

Postępuj zgodnie z dokumentacją dotyczącą zarządzania indeksami, aby tworzyć indeksy. Aby mieć pewność, że zapytanie może korzystać z indeksów, utwórz zwykłe indeksy (nie Multikey) z polami w tej kolejności:

  1. Wszystkie pola, które będą używane w operatorach równości. Aby zmaksymalizować szansę ponownego użycia w różnych zapytaniach, uporządkuj pola w kolejności malejącej liczby wystąpień pól w operatorach równości w zapytaniach.
  2. Wszystkie pola, według których będzie przeprowadzane sortowanie (w tej samej kolejności).
  3. Pola, które będą używane w operatorach zakresu lub nierówności w kolejności malejącej selektywności ograniczeń zapytania.
  4. Pola, które będą zwracane w ramach zapytania w indeksie: uwzględnienie takich pól w indeksie umożliwia mu obsługę zapytania i uniknięcie pobierania dokumentu z pamięci podstawowej.

Wymuszanie skanowania indeksu lub tabeli

Gdy wysyłasz zapytanie do Cloud Firestore, automatycznie używa ono indeksów, które prawdopodobnie zwiększą wydajność zapytania. Dzięki temu nie musisz określać indeksu w zapytaniach. W przypadku zapytań, które mają kluczowe znaczenie dla Twojego obciążenia, zalecamy jednak używanie opcji forceIndex, aby uzyskać bardziej spójną wydajność.

W niektórych przypadkach usługa Cloud Firestore może wybrać indeks, który powoduje wzrost opóźnienia zapytania. Jeśli po wykonaniu czynności związanych z rozwiązywaniem problemów z regresją wydajności stwierdzisz, że warto wypróbować inny indeks dla zapytania, możesz go określić za pomocą opcji forceIndex.

W przypadku dowolnego etapu wprowadzania danych w operacjach potoku możesz użyć opcji forceIndex, aby zastąpić domyślny plan zapytania Cloud Firestore i określić indeks do użycia lub wymusić skanowanie tabeli.

Wymuszanie konkretnego indeksu

Aby wymusić użycie określonego indeksu w zapytaniu, podaj identyfikator indeksu jako ciąg znaków w opcji forceIndex. Identyfikator indeksu znajdziesz w konsoli lub w komunikatach o błędach.

W tym przykładzie wymuszamy użycie przez optymalizator indeksu o identyfikatorze CICAgOi36pgK:

Node.js
// Force Planner to use Index ID CICAgOi36pgK
await db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "CICAgOi36pgK" })
  .limit(100)
  .execute();
Java
// Force Planner to use Index ID CICAgOi36pgK
Pipeline.Snapshot results1 =
    firestore.pipeline()
      .collectionGroup("customers", new CollectionGroupOptions()
          .withHints(new CollectionHints().withForceIndex("CICAgOi36pgK")))
      .limit(100)
      .execute().get();
Go
// Force Planner to use Index ID CICAgOi36pgK
snapshot1 := client.Pipeline().
	CollectionGroup("customers", firestore.WithForceIndex("CICAgOi36pgK")).
	Limit(100).
	Execute(ctx)

Oto kilka przypadków użycia wymuszania konkretnego indeksu:

  • testowanie skuteczności różnych indeksów,
  • zapewnienie, że w przypadku zapytania używany jest konkretny, optymalny indeks;
  • Zastępowanie optymalizatora, gdy jego domyślny wybór jest nieoptymalny w przypadku konkretnego zapytania.

Jeśli określony indeks nie zostanie znaleziony, zapytanie się nie powiedzie.

Wymuszanie skanowania tabeli

Skanowanie tabeli odczytuje dokumenty w kolekcji lub grupie kolekcji bez używania indeksów dodatkowych. Aby wymusić skanowanie tabeli, ustaw wartość forceIndex na primary.

Poniższy przykład wymusza skanowanie tabeli:

// Force Planner to only do a Full-Table Scan
db.pipeline()
  .collectionGroup({ collectionId: "customers", forceIndex: "primary" })
  .limit(100)

Skanowanie tabeli może być przydatne w tych przypadkach:

  • W przypadku bardzo małych kolekcji, w których narzut indeksu nie jest uzasadniony.
  • W przypadku zapytań, które uzyskują dostęp do większości dokumentów w kolekcji.
  • Do debugowania i porównywania wydajności.

Używanie forceIndex z wyjaśnieniem zapytania

Możesz użyć wyjaśnienia zapytania, zwłaszcza z opcją analyze, aby obserwować efekty forceIndex:

  • Sprawdź, czy Cloud Firestore użył określonego indeksu w forceIndex, przeglądając węzły liści drzewa wykonania pod kątem identyfikatora indeksu.
  • Sprawdź, czy w planie pojawia się węzeł TableScan, gdy używasz forceIndex: "primary".
  • Porównaj dane o skuteczności, takie jak opóźnienie, liczba skanowanych dokumentów i liczba skanowanych wpisów indeksu, z użyciem i bez użycia parametru forceIndex, aby dostroić wydajność zapytań.

Sprawdzone metody dotyczące forceIndex

forceIndex zapewnia większą kontrolę nad wykonywaniem zapytań, ale optymalizator zapytań w Cloud Firestore jest zwykle wystarczający w większości przypadków użycia. Podczas korzystania z atrybutu forceIndex pamiętaj o tych sprawdzonych metodach:

  • Używaj forceIndex z rozwagą. Jeśli zauważysz, że domyślny plan zapytania nie zapewnia optymalnej wydajności, użyj funkcji Query Explain, aby zdiagnozować problem przed wymuszeniem indeksu.
  • Gdy używasz forceIndex, testuj zapytania na realistycznych ilościach danych, aby poznać ich wydajność i charakterystykę kosztów.
  • Unikaj używania forceIndex: "primary" w przypadku dużych kolekcji w środowiskach produkcyjnych.