Overzicht van indexeren in Azure Cosmos DB

VAN TOEPASSING OP: Nosql MongoDB Cassandra Gremlin Tabel

Azure Cosmos DB is een schema-agnostische database waarmee u uw toepassing kunt herhalen zonder dat u te maken hebt met schema- of indexbeheer. Standaard indexeert Azure Cosmos DB automatisch elke eigenschap voor alle items in uw container zonder dat u een schema hoeft te definiëren of secundaire indexen hoeft te configureren.

Het doel van dit artikel is om uit te leggen hoe gegevens worden geïndexeerd met Azure Cosmos DB en hoe indexen worden gebruikt om de queryprestaties te verbeteren. Het is raadzaam deze sectie te doorlopen voordat u het indexeringsbeleid aanpast.

Van items naar structuren

Telkens wanneer een item wordt opgeslagen in een container, wordt de inhoud ervan geprojecteerd als een JSON-document en vervolgens geconverteerd naar een structuurweergave. Deze conversie betekent dat elke eigenschap van dat item wordt weergegeven als een knooppunt in een structuur. Een pseudo-hoofdknooppunt wordt gemaakt als een bovenliggend knooppunt voor alle eigenschappen op het eerste niveau van het item. De leaf-knooppunten bevatten de werkelijke scalaire waarden die door een item worden gedragen.

Als voorbeeld kunt u het volgende item bekijken:

{
  "locations": [
    { "country": "Germany", "city": "Berlin" },
    { "country": "France", "city": "Paris" }
  ],
  "headquarters": { "country": "Belgium", "employees": 250 },
  "exports": [
    { "city": "Moscow" },
    { "city": "Athens" }
  ]
}

Deze structuur vertegenwoordigt het JSON-voorbeelditem:

Diagram van het vorige JSON-item weergegeven als een structuur.

U ziet hoe matrices worden gecodeerd in de structuur: elke vermelding in een matrix krijgt een tussenliggend knooppunt met het label van de index van die vermelding in de matrix (0, 1 enzovoort).

Van bomen naar eigenschapspaden

De reden waarom Azure Cosmos DB items transformeert in structuren, is omdat het systeem hiermee naar eigenschappen kan verwijzen met behulp van hun paden binnen die structuren. Om het pad voor een eigenschap op te halen, kunnen we de structuur van het hoofdknooppunt naar die eigenschap doorkruisen en de labels van elk doorkruist knooppunt samenvoegen.

Dit zijn de paden voor elke eigenschap uit het voorbeelditem dat eerder is beschreven:

  • /locations/0/country: "Duitsland"
  • /locations/0/city: "Berlijn"
  • /locations/1/country: "Frankrijk"
  • /locations/1/city: "Parijs"
  • /headquarters/country: "België"
  • /headquarters/employees: 250
  • /exports/0/city: "Moskou"
  • /exports/1/city: "Athene"

Azure Cosmos DB indexeert effectief het pad van elke eigenschap en de bijbehorende waarde wanneer een item wordt geschreven.

Typen indexen

Azure Cosmos DB ondersteunt momenteel drie typen indexen. U kunt deze indextypen configureren bij het definiëren van het indexeringsbeleid.

Bereikindex

Bereikindexen zijn gebaseerd op een geordende structuur die lijkt op een structuur. Het type bereikindex wordt gebruikt voor:

  • Gelijkheidsquery's:

    SELECT * FROM container c WHERE c.property = 'value'
    
    SELECT * FROM c WHERE c.property IN ("value1", "value2", "value3")
    
  • Gelijkheidsmatch voor een matrixelement

    SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, "tag1")
    
  • Bereikquery's:

    SELECT * FROM container c WHERE c.property > 'value'
    

    Notitie

    (werkt voor >, <, >=, <=, ) !=

  • Controleren op de aanwezigheid van een eigenschap:

    SELECT * FROM c WHERE IS_DEFINED(c.property)
    
  • Systeemfuncties voor tekenreeksen:

    SELECT * FROM c WHERE CONTAINS(c.property, "value")
    
    SELECT * FROM c WHERE STRINGEQUALS(c.property, "value")
    
  • ORDER BY Query 's:

    SELECT * FROM container c ORDER BY c.property
    
  • JOIN Query 's:

    SELECT child FROM container c JOIN child IN c.properties WHERE child = 'value'
    

Bereikindexen kunnen worden gebruikt voor scalaire waarden (tekenreeks of getal). Het standaardindexeringsbeleid voor nieuw gemaakte containers dwingt bereikindexen af voor een willekeurige tekenreeks of een willekeurig getal. Zie Voorbeelden van bereikindexeringsbeleid voor meer informatie over het configureren van bereikindexen

Notitie

Een ORDER BY component die wordt georded op basis van één eigenschap, heeft altijd een bereikindex nodig en mislukt als het pad waarnaar wordt verwezen er geen heeft. Op dezelfde manier heeft een ORDER BY query die op meerdere eigenschappen ordet , altijd een samengestelde index nodig.

Ruimtelijke index

Met ruimtelijke indexen kunt u efficiënte query's uitvoeren op georuimtelijke objecten, zoals punten, lijnen, veelhoeken en multipolygonen. Deze query's gebruiken ST_DISTANCE, ST_WITHIN ST_INTERSECTS trefwoorden. Hier volgen enkele voorbeelden die gebruikmaken van het type ruimtelijke index:

  • Query's op georuimtelijke afstand:

    SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40
    
  • Georuimtelijk binnen query's:

    SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
    
  • Georuimtelijke intersect-query's:

    SELECT * FROM c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]]  })  
    

Ruimtelijke indexen kunnen worden gebruikt voor juist opgemaakte GeoJSON-objecten . Punten, lijntekenreeksen, veelhoeken en multipolygonen worden momenteel ondersteund. Zie Voorbeelden van beleid voor ruimtelijke indexering voor meer informatie over het configureren van ruimtelijke indexen

Samengestelde indexen

Samengestelde indexen verhogen de efficiëntie wanneer u bewerkingen uitvoert op meerdere velden. Het samengestelde indextype wordt gebruikt voor:

  • ORDER BY query's uitvoeren op meerdere eigenschappen:

    SELECT * FROM container c ORDER BY c.property1, c.property2
    
  • Query's met een filter en ORDER BY. Deze query's kunnen gebruikmaken van een samengestelde index als de filtereigenschap wordt toegevoegd aan de ORDER BY component.

    SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2
    
  • Query's met een filter op twee of meer eigenschappen waarbij ten minste één eigenschap een gelijkheidsfilter is

    SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
    

Zolang één filterpredicaat een van het indextype gebruikt, evalueert de query-engine dat eerst voordat de rest wordt gescand. Als u bijvoorbeeld een SQL-query hebt, zoals SELECT * FROM c WHERE c.firstName = "Andrew" and CONTAINS(c.lastName, "Liu")

  • De bovenstaande query filtert eerst op vermeldingen waarbij firstName = "Andrew" met behulp van de index. Vervolgens worden alle firstName = 'Andrew'-vermeldingen doorgegeven via een volgende pijplijn om het predicaat CONTAINS-filter te evalueren.

  • U kunt query's versnellen en volledige containerscans voorkomen wanneer u functies gebruikt die een volledige scan uitvoeren, zoals CONTAINS. U kunt meer filterpredicaten toevoegen die de index gebruiken om deze query's te versnellen. De volgorde van filtercomponenten is niet belangrijk. De query-engine zoekt uit welke predicaten selectiever zijn en voer de query dienovereenkomstig uit.

Zie Voorbeelden van samengesteld indexeringsbeleid voor meer informatie over het configureren van samengestelde indexen

Indexgebruik

Er zijn vijf manieren waarop de query-engine queryfilters kan evalueren, gesorteerd op meest efficiënt naar minst efficiënt:

  • Indexzoeken
  • Nauwkeurige indexscan
  • Uitgebreide indexscan
  • Volledige indexscan
  • Volledige scan

Wanneer u eigenschapspaden indexeren, maakt de query-engine automatisch zo efficiënt mogelijk gebruik van de index. Afgezien van het indexeren van nieuwe eigenschapspaden, hoeft u niets te configureren om te optimaliseren hoe query's de index gebruiken. De RU-kosten van een query zijn een combinatie van zowel de RU-kosten uit indexgebruik als de RU-kosten voor het laden van items.

Hier volgt een tabel met een overzicht van de verschillende manieren waarop indexen worden gebruikt in Azure Cosmos DB:

Type indexzoekactie Description Algemene voorbeelden RU-kosten uit indexgebruik RU-kosten voor het laden van items uit transactioneel gegevensarchief
Indexzoeken Alleen vereiste geïndexeerde waarden lezen en alleen overeenkomende items uit het transactionele gegevensarchief laden Gelijkheidsfilters, IN Constante per gelijkheidsfilter Verhogingen op basis van het aantal items in queryresultaten
Nauwkeurige indexscan Binair zoeken naar geïndexeerde waarden en alleen overeenkomende items laden uit het transactionele gegevensarchief Bereikvergelijkingen (>, <, <=of >=), StartsWith Vergelijkbaar met indexzoeken, neemt licht toe op basis van de kardinaliteit van geïndexeerde eigenschappen Verhogingen op basis van het aantal items in queryresultaten
Uitgebreide indexscan Geoptimaliseerd zoeken (maar minder efficiënt dan een binaire zoekopdracht) van geïndexeerde waarden en alleen overeenkomende items laden uit het transactionele gegevensarchief StartsWith (niet hoofdlettergevoelig), StringEquals (niet hoofdlettergevoelig) Neemt licht toe op basis van de kardinaliteit van geïndexeerde eigenschappen Verhogingen op basis van het aantal items in queryresultaten
Volledige indexscan Afzonderlijke set geïndexeerde waarden lezen en alleen overeenkomende items uit het transactionele gegevensarchief laden Contains, EndsWith, RegexMatch, LIKE Lineair verhoogd op basis van de kardinaliteit van geïndexeerde eigenschappen Verhogingen op basis van het aantal items in queryresultaten
Volledige scan Alle items uit het transactionele gegevensarchief laden Boven, Onder N.v.t. Verhogingen op basis van het aantal items in de container

Bij het schrijven van query's moet u filterpredicaten gebruiken die de index zo efficiënt mogelijk gebruiken. Als een of Contains bijvoorbeeld StartsWith werkt voor uw use-case, moet u ervoor kiezen StartsWith omdat hiermee een nauwkeurige indexscan wordt uitgevoerd in plaats van een volledige indexscan.

Details van indexgebruik

In deze sectie wordt meer informatie behandeld over hoe query's indexen gebruiken. Dit detailniveau is niet nodig om aan de slag te gaan met Azure Cosmos DB, maar wordt gedetailleerd gedocumenteerd voor nieuwsgierige gebruikers. We verwijzen naar het voorbeelditem dat eerder in dit document is gedeeld:

Voorbeelditems:

{
  "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" }
  ]
}

Azure Cosmos DB maakt gebruik van een omgekeerde index. De index werkt door elk JSON-pad toe te koppelen aan de set items die die waarde bevatten. De toewijzing van de item-id wordt weergegeven op veel verschillende indexpagina's voor de container. Hier volgt een voorbeelddiagram van een omgekeerde index voor een container met de twee voorbeelditems:

Pad Waarde Lijst met item-id's
/locations/0/country Duitsland 1
/locations/0/country Ierland 2
/locations/0/city Berlijn 1
/locations/0/city Dublin 2
/locaties/1/land Frankrijk 1
/locations/1/city Parijs 1
/hoofdkantoor/land België 1, 2
/hoofdkantoor/werknemers 200 2
/hoofdkantoor/werknemers 250 1

De omgekeerde index heeft twee belangrijke kenmerken:

  • Voor een bepaald pad worden waarden in oplopende volgorde gesorteerd. Daarom kan de query-engine eenvoudig worden uitgevoerd ORDER BY vanuit de index.
  • Voor een bepaald pad kan de query-engine de afzonderlijke set mogelijke waarden scannen om de indexpagina's met resultaten te identificeren.

De query-engine kan de omgekeerde index op vier verschillende manieren gebruiken:

Indexzoeken

Laten we nu eens naar deze query kijken:

SELECT location
FROM location IN company.locations
WHERE location.country = 'France'`

Het querypredicaat (filteren op items waarbij een locatie 'Frankrijk' als land/regio heeft) komt overeen met het pad dat in dit diagram is gemarkeerd:

Diagram van een doorkruising (zoeken) die overeenkomt met een specifiek pad binnen een structuur.

Omdat deze query een gelijkheidsfilter heeft, kunnen we na het doorlopen van deze structuur snel de indexpagina's identificeren die de queryresultaten bevatten. In dit geval leest de query-engine indexpagina's die Item 1 bevatten. Een indexzoekfunctie is de meest efficiënte manier om de index te gebruiken. Met een indexzoekactie lezen we alleen de benodigde indexpagina's en laden we alleen de items in de queryresultaten. Daarom zijn de opzoektijd van de index en de RU-kosten van indexzoekacties ongelooflijk laag, ongeacht het totale gegevensvolume.

Nauwkeurige indexscan

Laten we nu eens naar deze query kijken:

SELECT *
FROM company
WHERE company.headquarters.employees > 200

Het querypredicaat (filteren op items met meer dan 200 werknemers) kan worden geëvalueerd met een nauwkeurige indexscan van het headquarters/employees pad. Wanneer u een nauwkeurige indexscan uitvoert, begint de query-engine met een binaire zoekopdracht van de afzonderlijke set mogelijke waarden om de locatie van de waarde 200 voor het headquarters/employees pad te vinden. Omdat de waarden voor elk pad in oplopende volgorde worden gesorteerd, kan de query-engine eenvoudig een binaire zoekopdracht uitvoeren. Nadat de query-engine de waarde 200heeft gevonden, begint deze met het lezen van alle resterende indexpagina's (in oplopende richting).

Omdat de query-engine een binaire zoekopdracht kan uitvoeren om het scannen van onnodige indexpagina's te voorkomen, hebben nauwkeurige indexscans meestal vergelijkbare latentie en RU-kosten voor indexzoekbewerkingen.

Uitgebreide indexscan

Laten we nu eens naar deze query kijken:

SELECT *
FROM company
WHERE STARTSWITH(company.headquarters.country, "United", true)

Het querypredicaat (filteren op items die het hoofdkantoor hebben op een locatie die begint met niet-hoofdlettergevoelig 'United') kan worden geëvalueerd met een uitgebreide indexscan van het headquarters/country pad. Bewerkingen die een uitgebreide indexscan uitvoeren, hebben optimalisaties die kunnen helpen voorkomen dat elke indexpagina moet worden gescand, maar zijn iets duurder dan de binaire zoekopdracht van een indexscan.

Als u bijvoorbeeld niet hoofdlettergevoelig StartsWithevalueert, controleert de query-engine de index op verschillende mogelijke combinaties van hoofdletters en kleine letters. Dankzij deze optimalisatie kan de query-engine voorkomen dat de meeste indexpagina's worden gelezen. Verschillende systeemfuncties hebben verschillende optimalisaties die ze kunnen gebruiken om te voorkomen dat elke indexpagina wordt gelezen, zodat ze algemeen worden gecategoriseerd als uitgebreide indexscan.

Volledige indexscan

Laten we nu eens naar deze query kijken:

SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")

Het querypredicaat (filteren op items met een hoofdkantoor op een locatie die 'Verenigd' bevat) kan worden geëvalueerd met een indexscan van het headquarters/country pad. In tegenstelling tot een nauwkeurige indexscan scant een volledige indexscan altijd door de afzonderlijke set mogelijke waarden om de indexpagina's te identificeren met resultaten. In dit geval Contains wordt uitgevoerd op de index. De tijd voor indexzoektijd en RU-kosten voor indexscans nemen toe naarmate de kardinaliteit van het pad toeneemt. Met andere woorden, hoe meer mogelijke afzonderlijke waarden de query-engine moet scannen, hoe hoger de latentie en ru-kosten die nodig zijn voor het uitvoeren van een volledige indexscan.

Denk bijvoorbeeld aan twee eigenschappen: town en country. De kardinaliteit van de stad is 5.000 en de kardinaliteit van country is 200. Hier volgen twee voorbeeldquery's die elk een systeemfunctie bevatten waarmee een volledige indexscan op de town eigenschap wordt uitgevoerd. De eerste query gebruikt meer RU's dan de tweede query omdat de kardinaliteit van plaats hoger is dan country.

SELECT *
FROM c
WHERE CONTAINS(c.town, "Red", false)
SELECT *
FROM c
WHERE CONTAINS(c.country, "States", false)

Volledige scan

In sommige gevallen kan de query-engine een queryfilter mogelijk niet evalueren met behulp van de index. In dit geval moet de query-engine alle items uit de transactionele opslag laden om het queryfilter te kunnen evalueren. Volledige scans maken geen gebruik van de index en hebben een RU-kosten die lineair toenemen met de totale gegevensgrootte. Gelukkig zijn bewerkingen waarvoor volledige scans nodig zijn, zeldzaam.

Query's met complexe filterexpressies

In de eerdere voorbeelden hebben we alleen rekening gehouden met query's met eenvoudige filterexpressies (bijvoorbeeld query's met slechts één gelijkheids- of bereikfilter). In werkelijkheid hebben de meeste query's veel complexere filterexpressies.

Laten we nu eens naar deze query kijken:

SELECT *
FROM company
WHERE company.headquarters.employees = 200 AND CONTAINS(company.headquarters.country, "United")

Als u deze query wilt uitvoeren, moet de query-engine een indexzoekfunctie uitvoeren op headquarters/employees en een volledige indexscan uitvoeren op headquarters/country. De query-engine heeft interne heuristieken die worden gebruikt om de queryfilterexpressie zo efficiënt mogelijk te evalueren. In dit geval voorkomt de query-engine dat onnodige indexpagina's moeten worden gelezen door eerst de indexzoekactie uit te voeren. Als bijvoorbeeld slechts 50 items overeenkomen met het gelijkheidsfilter, hoeft de query-engine alleen te evalueren Contains op de indexpagina's die deze 50 items bevatten. Een volledige indexscan van de hele container is niet nodig.

Indexgebruik voor scalaire statistische functies

Query's met statistische functies moeten uitsluitend afhankelijk zijn van de index om deze te kunnen gebruiken.

In sommige gevallen kan de index fout-positieven retourneren. Bij het evalueren Contains van de index kan het aantal overeenkomsten in de index bijvoorbeeld groter zijn dan het aantal queryresultaten. De query-engine laadt alle indexovereenkomsten, evalueert het filter op de geladen items en retourneert alleen de juiste resultaten.

Voor de meeste query's heeft het laden van fout-positieve indexovereenkomsten geen merkbaar effect op het indexgebruik.

Kijk bijvoorbeeld eens naar de volgende query:

SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")

De Contains systeemfunctie kan enkele fout-positieve overeenkomsten retourneren, dus de query-engine moet controleren of elk geladen item overeenkomt met de filterexpressie. In dit voorbeeld hoeft de query-engine mogelijk slechts enkele extra items te laden, zodat het effect op het indexgebruik en de RU-kosten minimaal is.

Query's met statistische functies moeten echter uitsluitend afhankelijk zijn van de index om deze te kunnen gebruiken. Bekijk bijvoorbeeld de volgende query met een Count aggregatie:

SELECT COUNT(1)
FROM company
WHERE CONTAINS(company.headquarters.country, "United")

Net als in het eerste voorbeeld kan de Contains systeemfunctie enkele fout-positieve overeenkomsten retourneren. In tegenstelling tot de SELECT * query kan de Count query de filterexpressie voor de geladen items echter niet evalueren om alle indexovereenkomsten te controleren. De Count query moet uitsluitend afhankelijk zijn van de index, dus als er een kans is dat een filterexpressie fout-positieve overeenkomsten retourneert, wordt een volledige scan uitgevoerd door de query-engine.

Query's met de volgende statistische functies moeten uitsluitend afhankelijk zijn van de index, dus voor het evalueren van sommige systeemfuncties is een volledige scan vereist.

Volgende stappen

Meer informatie over indexeren vindt u in de volgende artikelen: