Omówienie indeksowania w usłudze Azure Cosmos DB
DOTYCZY: Nosql Mongodb Cassandra Gremlin Tabeli
Azure Cosmos DB to niezależna od schematu baza danych, która umożliwia iterowanie aplikacji bez konieczności zarządzania schematami lub indeksami. Domyślnie usługa Azure Cosmos DB automatycznie indeksuje każdą właściwość dla wszystkich elementów w kontenerze bez konieczności definiowania żadnego schematu ani konfigurowania indeksów pomocniczych.
Celem tego artykułu jest wyjaśnienie sposobu indeksowania danych przez usługę Azure Cosmos DB i wykorzystania tych indeksów do podwyższenia wydajności zapytań. Zaleca się przejście przez tę sekcję przed zbadaniem sposobu dostosowywania zasad indeksowania.
Od elementów do drzew
Za każdym razem, gdy element jest przechowywany w kontenerze, jego zawartość jest projektowana jako dokument JSON, a następnie konwertowana na reprezentację drzewa. Ta konwersja oznacza, że każda właściwość tego elementu jest reprezentowana jako węzeł w drzewie. Pseudowęźle główny jest tworzony jako element nadrzędny dla wszystkich właściwości pierwszego poziomu elementu. Węzły liścia zawierają rzeczywiste wartości skalarne przenoszone przez element.
Rozważmy na przykład ten element:
{
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
}
To drzewo reprezentuje przykładowy element JSON:
Zwróć uwagę, jak tablice są kodowane w drzewie: każdy wpis w tablicy otrzymuje węzeł pośredni oznaczony indeksem tego wpisu w tablicy (0, 1 itp.).
Od drzew do ścieżek właściwości
Powodem, dla którego usługa Azure Cosmos DB przekształca elementy w drzewa, jest to, że umożliwia systemowi odwołanie się do właściwości przy użyciu ich ścieżek w tych drzewach. Aby uzyskać ścieżkę dla właściwości, możemy przejść przez drzewo z węzła głównego do tej właściwości i połączyć etykiety każdego węzła przechodzącego.
Poniżej przedstawiono ścieżki dla każdej właściwości z przykładowego elementu opisanego wcześniej:
/locations/0/country
: "Niemcy"/locations/0/city
: "Berlin"/locations/1/country
: "Francja"/locations/1/city
: "Paryż"/headquarters/country
: "Belgia"/headquarters/employees
: 250/exports/0/city
: "Moskwa"/exports/1/city
: "Ateny"
Usługa Azure Cosmos DB skutecznie indeksuje ścieżkę każdej właściwości i jej odpowiednią wartość po zapisaniu elementu.
Typy indeksów
Usługa Azure Cosmos DB obecnie obsługuje trzy typy indeksów. Te typy indeksów można skonfigurować podczas definiowania zasad indeksowania.
Indeks zakresu
Indeksy zakresu są oparte na uporządkowanej strukturze podobnej do drzewa. Typ indeksu zakresu jest używany dla:
Zapytania równości:
SELECT * FROM container c WHERE c.property = 'value'
SELECT * FROM c WHERE c.property IN ("value1", "value2", "value3")
Dopasowanie równości w elemecie tablicy
SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, "tag1")
Zapytania zakresu:
SELECT * FROM container c WHERE c.property > 'value'
Uwaga
(działa dla
>
, ,<
,>=
,!=
<=
)Sprawdzanie obecności właściwości:
SELECT * FROM c WHERE IS_DEFINED(c.property)
Funkcje systemu ciągów:
SELECT * FROM c WHERE CONTAINS(c.property, "value")
SELECT * FROM c WHERE STRINGEQUALS(c.property, "value")
ORDER BY
Kwerendy:SELECT * FROM container c ORDER BY c.property
JOIN
Kwerendy:SELECT child FROM container c JOIN child IN c.properties WHERE child = 'value'
Indeksy zakresu mogą być używane w wartościach skalarnych (ciąg lub liczba). Domyślne zasady indeksowania dla nowo utworzonych kontenerów wymuszają indeksy zakresu dla wszelkich ciągów i liczb. Aby dowiedzieć się, jak skonfigurować indeksy zakresu, zobacz Przykłady zasad indeksowania zakresu
Uwaga
Klauzula ORDER BY
, która porządkuje pojedynczą właściwość zawsze wymaga indeksu zakresu i zakończy się niepowodzeniem, jeśli ścieżka, do której się odwołuje, nie ma takiego indeksu. Podobnie ORDER BY
zapytanie, które zamówienia według wielu właściwości zawsze wymaga indeksu złożonego.
Indeks przestrzenny
Indeksy przestrzenne umożliwiają wydajne zapytania dotyczące obiektów geoprzestrzennych, takich jak punkty, linie, wielokąty i multipolygon. Te zapytania używają słów kluczowych ST_DISTANCE, ST_WITHIN ST_INTERSECTS. Poniżej przedstawiono kilka przykładów, które używają typu indeksu przestrzennego:
Zapytania dotyczące odległości geoprzestrzennych:
SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40
Geoprzestrzenne w zapytaniach:
SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
Zapytania interprzestrzenne geoprzestrzenne:
SELECT * FROM c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]] })
Indeksy przestrzenne mogą być używane na poprawnie sformatowanych obiektach GeoJSON . Obecnie obsługiwane są punkty, ciągi liniowe, wielokąty i wielokąty. Aby dowiedzieć się, jak skonfigurować indeksy przestrzenne, zobacz Przykłady zasad indeksowania przestrzennego
Indeksy złożone
Indeksy złożone zwiększają wydajność podczas wykonywania operacji na wielu polach. Typ indeksu złożonego jest używany w następujących celach:
ORDER BY
zapytania dotyczące wielu właściwości:SELECT * FROM container c ORDER BY c.property1, c.property2
Zapytania z filtrem i
ORDER BY
. Te zapytania mogą korzystać z indeksu złożonego, jeśli właściwość filtru zostanie dodana do klauzuliORDER BY
.SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2
Zapytania z filtrem w co najmniej dwóch właściwościach, w których co najmniej jedna właściwość jest filtrem równości
SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
Tak długo, jak jeden predykat filtru używa jednego z typów indeksów, aparat zapytań ocenia, że najpierw przed skanowaniem reszty. Jeśli na przykład masz zapytanie SQL, takie jak SELECT * FROM c WHERE c.firstName = "Andrew" and CONTAINS(c.lastName, "Liu")
Powyższe zapytanie najpierw filtruje wpisy, w których firstName = "Andrew" przy użyciu indeksu. Następnie przekazuje wszystkie wpisy firstName = "Andrew" za pośrednictwem kolejnego potoku, aby ocenić predykat filtru CONTAINS.
Zapytania można przyspieszyć i uniknąć pełnego skanowania kontenerów podczas korzystania z funkcji wykonujących pełne skanowanie, takie jak CONTAINS. Możesz dodać więcej predykatów filtrów, które używają indeksu, aby przyspieszyć te zapytania. Kolejność klauzul filtru nie jest ważna. Aparat zapytań określa, które predykaty są bardziej selektywne i odpowiednio uruchamiają zapytanie.
Aby dowiedzieć się, jak skonfigurować indeksy złożone, zobacz Przykłady zasad indeksowania złożonego
Użycie indeksu
Istnieje pięć sposobów oceny filtrów zapytań przez aparat zapytań posortowanych według najbardziej wydajnych i najmniej wydajnych:
- Wyszukiwanie indeksu
- Dokładne skanowanie indeksu
- Rozwinięte skanowanie indeksu
- Pełne skanowanie indeksu
- Pełne skanowanie
Podczas indeksowania ścieżek właściwości aparat zapytań automatycznie używa indeksu tak wydajnie, jak to możliwe. Oprócz indeksowania nowych ścieżek właściwości nie trzeba konfigurować żadnych elementów w celu zoptymalizowania sposobu używania indeksu przez zapytania. Opłata za jednostkę RU zapytania jest kombinacją opłaty za jednostkę RU od użycia indeksu i opłaty za jednostkę RU z ładowania elementów.
Oto tabela podsumowująca różne sposoby użycia indeksów w usłudze Azure Cosmos DB:
Typ odnośnika indeksu | Opis | Typowe przykłady | Opłaty za jednostkę RU od użycia indeksu | Opłaty za jednostkę RU z ładowania elementów z transakcyjnego magazynu danych |
---|---|---|---|---|
Wyszukiwanie indeksu | Tylko do odczytu wymagane wartości indeksowane i ładuj tylko pasujące elementy z transakcyjnego magazynu danych | Filtry równości, IN | Stała na filtr równości | Zwiększa się w oparciu o liczbę elementów w wynikach zapytania |
Dokładne skanowanie indeksów | Binarne wyszukiwanie indeksowanych wartości i ładowanie tylko pasujących elementów z transakcyjnego magazynu danych | Porównania zakresów (>, <, <= lub >=), StartsWith | Porównywalne z wyszukiwaniem indeksu, nieznacznie zwiększa się w zależności od kardynalności indeksowanych właściwości | Zwiększa się w oparciu o liczbę elementów w wynikach zapytania |
Rozszerzone skanowanie indeksów | Zoptymalizowane wyszukiwanie (ale mniej wydajne niż wyszukiwanie binarne) indeksowanych wartości i ładowanie tylko pasujących elementów z transakcyjnego magazynu danych | StartsWith (bez uwzględniania wielkości liter), StringEquals (bez uwzględniania wielkości liter) | Nieznacznie zwiększa się w zależności od kardynalności indeksowanych właściwości | Zwiększa się w oparciu o liczbę elementów w wynikach zapytania |
Pełne skanowanie indeksu | Odczytywanie odrębnego zestawu indeksowanych wartości i ładowanie tylko pasujących elementów z transakcyjnego magazynu danych | Contains, EndsWith, RegexMatch, LIKE | Zwiększa liniowo na podstawie kardynalności indeksowanych właściwości | Zwiększa się w oparciu o liczbę elementów w wynikach zapytania |
Pełne skanowanie | Załaduj wszystkie elementy z transakcyjnego magazynu danych | Górna, Dolna | Nie dotyczy | Zwiększa się w zależności od liczby elementów w kontenerze |
Podczas pisania zapytań należy używać predykatów filtrów, które korzystają z indeksu tak wydajnie, jak to możliwe. Jeśli na przykład rozwiązanie StartsWith
lub Contains
będzie działać w twoim przypadku użycia, należy wybrać StartsWith
opcję dokładnego skanowania indeksu zamiast pełnego skanowania indeksu.
Szczegóły użycia indeksu
W tej sekcji opisano więcej szczegółów dotyczących używania indeksów przez zapytania. Ten poziom szczegółowości nie jest konieczny, aby dowiedzieć się, jak rozpocząć pracę z usługą Azure Cosmos DB, ale został szczegółowo udokumentowany dla ciekawych użytkowników. Odwołujemy się do przykładowego elementu udostępnionego wcześniej w tym dokumencie:
Przykładowe elementy:
{
"id": 1,
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
}
{
"id": 2,
"locations": [
{ "country": "Ireland", "city": "Dublin" }
],
"headquarters": { "country": "Belgium", "employees": 200 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" },
{ "city": "London" }
]
}
Usługa Azure Cosmos DB używa odwróconego indeksu. Indeks działa przez mapowanie każdej ścieżki JSON na zestaw elementów, które zawierają te wartości. Mapowanie identyfikatora elementu jest reprezentowane na wielu różnych stronach indeksu dla kontenera. Oto przykładowy diagram odwróconego indeksu dla kontenera zawierającego dwa przykładowe elementy:
Ścieżka | Wartość | Lista identyfikatorów elementów |
---|---|---|
/locations/0/country | Niemcy | 1 |
/locations/0/country | Irlandia | 2 |
/locations/0/city | Berlin | 1 |
/locations/0/city | Dublin | 2 |
/locations/1/country | Francja | 1 |
/locations/1/city | Paryż | 1 |
/centrala/kraj | Belgia | 1, 2 |
/centrala/pracownicy | 200 | 2 |
/centrala/pracownicy | 250 | 1 |
Odwrócony indeks ma dwa ważne atrybuty:
- Dla danej ścieżki wartości są sortowane w kolejności rosnącej. W związku z tym aparat zapytań może łatwo służyć
ORDER BY
z poziomu indeksu. - W przypadku danej ścieżki aparat zapytań może skanować za pomocą odrębnego zestawu możliwych wartości w celu zidentyfikowania stron indeksu, na których znajdują się wyniki.
Aparat zapytań może korzystać z odwróconego indeksu na cztery różne sposoby:
Wyszukiwanie indeksu
Rozpatrzmy następujące zapytanie:
SELECT location
FROM location IN company.locations
WHERE location.country = 'France'`
Predykat zapytania (filtrowanie elementów, w których dowolna lokalizacja ma wartość "Francja" jako kraj/region), będzie zgodna ze ścieżką wyróżnioną na tym diagramie:
Ponieważ to zapytanie ma filtr równości, po przejściu przez to drzewo możemy szybko zidentyfikować strony indeksu zawierające wyniki zapytania. W takim przypadku aparat zapytań odczytuje strony indeksu zawierające element 1. Wyszukiwanie indeksu jest najbardziej efektywnym sposobem korzystania z indeksu. W przypadku wyszukiwania indeksu odczytamy tylko niezbędne strony indeksu i załadujemy tylko elementy w wynikach zapytania. W związku z tym czas wyszukiwania indeksu i opłaty za jednostki RU z wyszukiwania indeksu są niezwykle niskie, niezależnie od całkowitej ilości danych.
Dokładne skanowanie indeksów
Rozpatrzmy następujące zapytanie:
SELECT *
FROM company
WHERE company.headquarters.employees > 200
Predykat zapytania (filtrowanie elementów, w których istnieje ponad 200 pracowników) można ocenić przy użyciu dokładnego skanowania indeksu headquarters/employees
ścieżki. Podczas dokładnego skanowania indeksu aparat zapytań rozpoczyna się od wykonania binarnego wyszukiwania odrębnego zestawu możliwych wartości w celu znalezienia lokalizacji wartości 200
dla ścieżki headquarters/employees
. Ponieważ wartości dla każdej ścieżki są sortowane w kolejności rosnącej, aparat zapytań może łatwo przeprowadzić wyszukiwanie binarne. Po znalezieniu wartości 200
przez aparat zapytań rozpoczyna odczytywanie wszystkich pozostałych stron indeksu (przechodzących w kierunku rosnącym).
Ponieważ aparat zapytań może wykonywać wyszukiwanie binarne, aby uniknąć skanowania niepotrzebnych stron indeksu, precyzyjne skanowania indeksów mają zwykle porównywalne opóźnienie i opłaty za jednostki RU do indeksowania operacji wyszukiwania.
Rozszerzone skanowanie indeksów
Rozpatrzmy następujące zapytanie:
SELECT *
FROM company
WHERE STARTSWITH(company.headquarters.country, "United", true)
Predykat zapytania (filtrowanie elementów, które mają siedzibę w lokalizacji rozpoczynającej się od bez uwzględniania wielkości liter "United") można ocenić za pomocą rozszerzonego skanowania indeksu headquarters/country
ścieżki. Operacje, które wykonują rozszerzone skanowanie indeksów, mają optymalizacje, które mogą pomóc uniknąć konieczności skanowania każdej strony indeksu, ale są nieco droższe niż wyszukiwanie binarne dokładnego skanowania indeksu.
Na przykład podczas oceniania bez uwzględniania StartsWith
wielkości liter aparat zapytań sprawdza indeks pod kątem różnych możliwych kombinacji wielkich i małych wartości. Ta optymalizacja pozwala aparatowi zapytań uniknąć odczytywania większości stron indeksu. Różne funkcje systemowe mają różne optymalizacje, których mogą używać, aby uniknąć odczytywania każdej strony indeksu, więc są one szeroko skategoryzowane jako rozszerzone skanowanie indeksów.
Pełne skanowanie indeksu
Rozpatrzmy następujące zapytanie:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Predykat zapytania (filtrowanie elementów mających siedzibę w lokalizacji zawierającej "United") można ocenić za pomocą skanowania indeksu headquarters/country
ścieżki. W przeciwieństwie do dokładnego skanowania indeksu, pełne skanowanie indeksu zawsze przeprowadza skanowanie za pomocą odrębnego zestawu możliwych wartości w celu zidentyfikowania stron indeksu, na których znajdują się wyniki. W tym przypadku Contains
polecenie jest uruchamiane w indeksie. Czas wyszukiwania indeksu i opłata za jednostki RU dla skanowania indeksu zwiększa się wraz ze wzrostem kardynalności ścieżki. Innymi słowy, im więcej możliwych odrębnych wartości, które aparat zapytań musi skanować, tym większe opóźnienie i opłaty za jednostki RU związane z wykonywaniem pełnego skanowania indeksu.
Rozważmy na przykład dwie właściwości: town
i country
. Kardynalność miasta wynosi 5000, a kardynalność country
wynosi 200. Poniżej przedstawiono dwa przykładowe zapytania, z których każda ma funkcję systemową Contains , która wykonuje pełne skanowanie indeksu we town
właściwości . Pierwsze zapytanie używa więcej jednostek RU niż drugie zapytanie, ponieważ kardynalność miasta jest wyższa niż country
.
SELECT *
FROM c
WHERE CONTAINS(c.town, "Red", false)
SELECT *
FROM c
WHERE CONTAINS(c.country, "States", false)
Pełne skanowanie
W niektórych przypadkach aparat zapytań może nie być w stanie ocenić filtru zapytania przy użyciu indeksu. W takim przypadku aparat zapytań musi załadować wszystkie elementy z magazynu transakcyjnego, aby ocenić filtr zapytania. Pełne skanowania nie korzystają z indeksu i mają opłatę za jednostkę RU, która zwiększa się liniowo wraz z całkowitym rozmiarem danych. Na szczęście operacje wymagające pełnego skanowania są rzadkie.
Zapytania z złożonymi wyrażeniami filtru
We wcześniejszych przykładach rozważaliśmy tylko zapytania, które miały proste wyrażenia filtru (na przykład zapytania z tylko jednym filtrem równości lub zakresu). W rzeczywistości większość zapytań ma znacznie bardziej złożone wyrażenia filtru.
Rozpatrzmy następujące zapytanie:
SELECT *
FROM company
WHERE company.headquarters.employees = 200 AND CONTAINS(company.headquarters.country, "United")
Aby wykonać to zapytanie, aparat zapytań musi wykonać wyszukiwanie indeksów i headquarters/employees
pełne skanowanie indeksu w usłudze headquarters/country
. Aparat zapytań ma wewnętrzne algorytmy heurystyczne, których używa do oceny wyrażenia filtru zapytania tak wydajnie, jak to możliwe. W takim przypadku aparat zapytań unikałby konieczności odczytywania niepotrzebnych stron indeksu, wykonując najpierw wyszukiwanie indeksu. Jeśli na przykład tylko 50 elementów pasuje do filtru równości, aparat zapytań będzie musiał ocenić Contains
tylko na stronach indeksu, które zawierały te 50 elementów. Pełne skanowanie indeksu całego kontenera nie byłoby konieczne.
Wykorzystanie indeksu dla funkcji agregacji skalarnych
Zapytania z funkcjami agregowanymi muszą polegać wyłącznie na indeksie, aby można było z niego korzystać.
W niektórych przypadkach indeks może zwracać wyniki fałszywie dodatnie. Na przykład podczas oceniania Contains
indeksu liczba dopasowań w indeksie może przekraczać liczbę wyników zapytania. Aparat zapytań ładuje wszystkie dopasowania indeksu, oblicza filtr załadowanych elementów i zwraca tylko poprawne wyniki.
W przypadku większości zapytań ładowanie wyników indeksu fałszywie dodatniego nie ma zauważalnego wpływu na wykorzystanie indeksu.
Rozważmy na przykład następujące zapytanie:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Funkcja systemowa Contains
może zwracać kilka wyników fałszywie dodatnich, więc aparat zapytań musi sprawdzić, czy każdy załadowany element jest zgodny z wyrażeniem filtru. W tym przykładzie aparat zapytań może wymagać załadowania tylko kilku dodatkowych elementów, więc wpływ na użycie indeksu i opłaty za jednostki RU jest minimalny.
Jednak zapytania z funkcjami agregowanymi muszą polegać wyłącznie na indeksie, aby można było z niego korzystać. Rozważmy na przykład następujące zapytanie zagregowane Count
:
SELECT COUNT(1)
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Podobnie jak w pierwszym przykładzie, funkcja systemowa Contains
może zwracać kilka wyników fałszywie dodatnich. SELECT *
W przeciwieństwie do zapytania zapytanie nie może jednak Count
ocenić wyrażenia filtru dla załadowanych elementów, aby zweryfikować wszystkie dopasowania indeksu. Zapytanie Count
musi polegać wyłącznie na indeksie, więc jeśli istnieje prawdopodobieństwo, że wyrażenie filtru zwróci wyniki fałszywie dodatnie, aparat zapytań zwraca pełne skanowanie.
Zapytania z następującymi funkcjami agregacji muszą polegać wyłącznie na indeksie, więc ocena niektórych funkcji systemowych wymaga pełnego skanowania.
Następne kroki
Przeczytaj więcej na temat indeksowania w następujących artykułach: