Condividi tramite


Indicizzazione in Cosmos DB in Microsoft Fabric

Cosmos DB è un database indipendente dallo schema che consente di scorrere l'applicazione senza dover gestire lo schema o la gestione degli indici. Questo schema viene anche definito schema in lettura, ovvero Cosmos DB non applica uno schema ai dati quando viene scritto nel database. Lo schema viene invece proiettato nelle classi definite all'interno dell'applicazione quando si deserializzano i dati dal database quando si leggono o si eseguono query sui dati.

L'indicizzazione all'interno di Cosmos DB in Microsoft Fabric è progettata per offrire prestazioni di query veloci e flessibili, indipendentemente dal modo in cui i dati si evolvono. Per impostazione predefinita, Cosmos DB indicizza automaticamente ogni proprietà per tutti gli elementi nel contenitore senza dover definire alcuno schema o configurare indici secondari.

Albero concettuale

Ogni volta che un elemento viene archiviato in un contenitore, il relativo contenuto viene proiettato come documento JSON e quindi convertito in una rappresentazione ad albero. Questa conversione implica che ogni proprietà di quell’elemento viene rappresentata come un “node” in un albero. Un nodo pseudoradice viene creato come elemento padre per tutte le proprietà di primo livello dell'elemento. I nodi foglia contengono i valori scalari effettivi di un elemento.

Ad esempio, considerare questo elemento:

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

Questo albero concettuale rappresenta l'elemento JSON di esempio:

  • locations
    • 0
      • country: Germany
      • city: Berlin
    • 1
      • country: France
      • city: Paris
  • headquarters
    • country: Belgium
    • employees: 250
  • exports
    • 0
      • city: Moscow
    • 1
      • city: Athens

Diagramma della rappresentazione ad albero di un elemento in Cosmos DB.

Diagramma ad albero che mostra un nodo radice con tre rami: "locations", "headquarters" e "export". "Posizioni" si suddivide in due nodi numerati, ognuno con due sottonodi correlati alla posizione ("Germania o Berlino" e "Francia o Parigi"). "Quartier generale" ha "Belgio" per la sua posizione e "dipendenti" ("250"). "Esportazioni" si divide in due nodi numerati, ognuno con un sottonodo "città" ("Mosca" e "Atene").

Prestare attenzione al modo in cui le matrici vengono codificate nell'albero: ogni voce di una matrice ottiene un nodo intermedio etichettato con l'indice di tale voce all'interno della matrice. Ad esempio, la prima voce è 0 e la seconda voce è 1.

Percorsi delle proprietà

Cosmos DB trasforma gli elementi in alberi perché consente al sistema di fare riferimento alle proprietà usando i percorsi all'interno di tali alberi. Per ottenere il percorso di una proprietà, è possibile eseguire l'attraversamento dell'albero dal nodo radice a tale proprietà e concatenare le etichette di ogni nodo attraversato.

Questi sono i percorsi per ogni proprietà dell'elemento di esempio descritto in precedenza:

Percorso Valore
/locations/0/country "Germany"
/locations/0/city "Berlin"
/locations/1/country "France"
/locations/1/city "Paris"
/headquarters/country "Belgium"
/headquarters/employees 250
/exports/0/city "Moscow"
/exports/1/city "Athens"

Cosmos DB indicizza in modo efficace il percorso di ogni proprietà e il relativo valore corrispondente quando viene scritto un elemento.

Tipi di indice

Cosmos DB supporta attualmente quattro tipi di indici.

È possibile configurare questi tipi di indice quando si definiscono i criteri di indicizzazione.

Indice di intervallo

Gli indici di Range si basano su una struttura ad albero ordinata. Si tratta del tipo di indice predefinito e non è necessario specificare quando si definiscono criteri di indice. L’indice di Range si usa per:

  • Query di uguaglianza:

    SELECT
      *
    FROM
      container c
    WHERE
      c.property = 'value'
    
    SELECT
      *
    FROM
      container c
    WHERE
      c.property IN ("value1", "value2", "value3")
    
  • Corrispondenza di uguaglianza su un elemento di matrice

    SELECT
      *
    FROM
      container c
    WHERE
      ARRAY_CONTAINS(c.tags, "tag1")
    
  • Query su intervallo:

    SELECT
      *
    FROM
      container c
    WHERE
      c.property > 0
    

    Annotazioni

    Funziona per >, <, >=, <=, !=

  • Verifica della presenza di una proprietà:

    SELECT
      *
    FROM
      container c
    WHERE
      IS_DEFINED(c.property)
    
  • Funzioni di sistema di stringa:

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

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

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

Gli indici di intervallo possono essere usati in valori scalari (stringa o numero). I criteri di indicizzazione predefiniti per i contenitori appena creati applicano indici di intervallo per qualsiasi stringa o numero.

Annotazioni

Una ORDER BY clausola che ordina in base a una singola proprietà richiede sempre un indice di intervallo e ha esito negativo se il percorso a cui fa riferimento non ne ha uno. Analogamente, una ORDER BY query che ordina per più proprietà richiede sempre un indice composito.

Indice spaziale

Gli indici spaziali consentono query efficienti su oggetti geospaziali, ad esempio punti, linee, poligoni e multipolygoni. Queste query usano le parole chiave ST_DISTANCE, ST_WITHIN, ST_INTERSECTS. Di seguito ci sono alcuni esempi che usano gli indici di tipo spaziale:

  • Query geospaziale distance:

    SELECT
      *
    FROM
      container c
    WHERE
      ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40
    
  • Query geospaziale within:

    SELECT
      *
    FROM
      container c
    WHERE
      ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
    
  • Query geospaziale intersect:

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

Gli indici spaziali possono essere usati su oggetti GeoJSON con formato corretto. Attualmente sono supportati gli oggetti Point, LineString, Polygon e MultiPolygon.

Indice composito

Gli indici composti aumentano l'efficienza quando esegui operazioni su più campi. L’indice di tipo composto si usa per:

  • Query ORDER BY su più proprietà:

    SELECT
      *
    FROM
      container c
    ORDER BY
      c.property1,
      c.property2
    
  • Query con un filtro e ORDER BY. Queste query possono usare un indice composto se la proprietà di filtro viene aggiunta alla clausola ORDER BY.

    SELECT
      *
    FROM
      container c
    WHERE
      c.property1 = 'value'
    ORDER BY
      c.property1,
      c.property2
    
  • Query con un filtro in base a due o più proprietà in cui almeno una proprietà è un filtro di uguaglianza:

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

Finché un predicato di filtro usa uno dei tipi di indice, il motore di query lo valuta prima di passare al resto. Ad esempio, se si dispone di una query SQL come SELECT * FROM c WHERE c.department = "Information Technology" and CONTAINS(c.team, "Pilot"):

  • Questa query applica prima un filtro per le voci dove department = "Information Technology", utilizzando l'indice. Passa quindi tutte le department = "Information Technology" voci tramite una pipeline successiva per valutare il predicato del CONTAINS filtro.

  • È possibile velocizzare le query ed evitare analisi complete dei contenitori quando si usano funzioni che eseguono un'analisi completa come CONTAINS. È possibile aggiungere altri predicati di filtro che usano l'indice per velocizzare queste query. L'ordine delle clausole di filtro non è importante. Il motore delle query individua i predicati più selettivi ed esegue la query di conseguenza.

Indice vettoriale

Gli indici vettoriali aumentano l'efficienza durante l'esecuzione di ricerche vettoriali usando la funzione di sistema VECTORDISTANCE. Le ricerche vettoriali hanno una latenza inferiore, una velocità effettiva più elevata e un consumo minore di UR quando si usa un indice vettoriale. Cosmos DB supporta qualsiasi embedding vettoriale (testo, immagine, multimodale, ecc.) fino a 4.096 dimensioni.

  • ORDER BY query di ricerca vettoriali:

    SELECT TOP 10
      *
    FROM
      container c
    ORDER BY
      VECTORDISTANCE(c.vector1, c.vector2)
    
  • Proiezione del punteggio di somiglianza nelle query di ricerca vettoriale:

    SELECT TOP 10
      c.name,
      VECTORDISTANCE(c.vector1, c.vector2) AS score
    FROM
      container c
    ORDER BY
      VECTORDISTANCE(c.vector1, c.vector2)
    
  • Filtri di intervallo per il punteggio di somiglianza.

    SELECT TOP 10
      *
    FROM
      container c
    WHERE
      VECTORDISTANCE(c.vector1, c.vector2) > 0.8
    ORDER BY
      VECTORDISTANCE(c.vector1, c.vector2)
    

Importante

I criteri vettoriali e gli indici vettoriali non sono modificabili dopo la creazione. Per apportare modifiche, creare una nuova raccolta.

Utilizzo dell'indice

Ci sono cinque modi in cui il motore di query può valutare i filtri di query, che vanno dal più efficiente e meno efficiente:

  • Ricerca nell'indice
  • Analisi precisa dell'indice
  • Analisi estesa dell'indice
  • Analisi completa dell'indice
  • Analisi completa

Per indicizzare i percorsi delle proprietà, il motore di query usa automaticamente l'indice nel modo più efficiente possibile. Oltre all'indicizzazione di nuovi percorsi di proprietà, non è necessario configurare nulla per ottimizzare il modo in cui le query usano l'indice. L'addebito UR di una query è una combinazione sia dell'addebito delle UR dall'utilizzo dell'indice che dell'addebito delle UR dal caricamento degli elementi.

La tabella seguente riepiloga i diversi modi in cui vengono usati gli indici in Cosmos DB:

Tipo Lookup Descrizione Esempi comuni Addebito dall'utilizzo dell'indice Addebiti per il caricamento di elementi dall'archivio dati transazionale
Ricerca nell'indice Legge solamente i valori indicizzati richiesti e carica solamente gli elementi corrispondenti dall'archivio dati transazionale Filtri di uguaglianza, IN Filtro di costante per uguaglianza Aumenta in base al numero di elementi nei risultati della query
Analisi precisa dell'indice Ricerca binaria di valori indicizzati e carica solo gli elementi corrispondenti dall'archivio dati transazionale Confronti tra range (>, <, <=, o >=), StartsWith Confrontabile con la ricerca di indici, aumenta leggermente in base alla cardinalità delle proprietà indicizzate Aumenta in base al numero di elementi nei risultati della query
Analisi dell'indice espansa Ricerca ottimizzata (ma meno efficiente di una ricerca binaria) di valori indicizzati e carica solo gli elementi corrispondenti dall'archivio dati transazionale StartsWith (senza distinzione tra maiuscole e minuscole), StringEquals (senza distinzione tra maiuscole e minuscole) Aumenta leggermente in base alla cardinalità delle proprietà indicizzate Aumenta in base al numero di elementi nei risultati della query
Analisi completa dell'indice Legge un set distinto di valori indicizzati e caricare solo gli elementi corrispondenti dall'archivio dati transazionale Contains, EndsWith, RegexMatch, LIKE Aumenta in modo lineare in base alla cardinalità delle proprietà indicizzate Aumenta in base al numero di elementi nei risultati della query
Analisi completa Carica tutti gli elementi dall'archivio dati transazionale Superiore, Inferiore Non disponibile Aumenta in base al numero di elementi nel contenitore

Quando si scrivono le query, usare i predicati di filtro che utilizzano l'indice nel modo più efficiente possibile. Ad esempio, se StartsWith o Contains dovessero andare bene per quello che è necessario fare, scegliere StartsWith dato che esegue un'analisi dell'indice precisa anziché un'analisi completa dell'indice.

Dettagli sull'utilizzo dell'indice

Suggerimento

In questa sezione vengono illustrati altri dettagli sul modo in cui le query usano gli indici. Questo livello di dettaglio non è necessario per apprendere come iniziare a usare Cosmos DB, ma è documentato in dettaglio per gli utenti curiosi. Si fa riferimento all'elemento di esempio condiviso in precedenza in questo documento:

Si considerino questi due elementi di esempio:

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

Cosmos DB usa un indice invertito. L'indice funziona eseguendo il mapping di ogni percorso JSON del set di elementi che contengono quel valore. Il mapping dell'ID dell’elemento viene rappresentato in svariate pagine dell’indice per il container. Di seguito vediamo un diagramma di un esempio di un indice invertito per un container che comprende i due elementi esempio:

Percorso Valore Elenco di identificatori di elemento
/locations/0/country Germany [1]
/locations/0/country Ireland [2]
/locations/0/city Berlin [1]
/locations/0/city Dublin [2]
/locations/1/country France [1]
/locations/1/city Paris [1]
/headquarters/country Belgium [1, 2]
/headquarters/employees 200 [2]
/headquarters/employees 250 [1]

L'indice invertito ha due caratteristiche importanti:

  • Per un determinato percorso, i valori vengono ordinati in ordine crescente. Quindi il motore di query può facilmente operare ORDER BY dall'indice.

  • Per un determinato percorso, il motore di query può analizzare il set distinto di possibili valori per identificare le pagine dell’indice dove sono presenti dei risultati.

Il motore di query può usare l'indice invertito in quattro modi diversi:

Ricerca nell'indice

Si consideri la seguente interrogazione:

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

Il predicato di query (applicazione di filtri sugli elementi in cui qualsiasi località ha "Francia" come area geografica) corrisponde al percorso indicato qui:

  • locations
    • 1
      • country: France

Diagramma di un attraversamento (ricerca) corrispondente a un percorso specifico all'interno di una rappresentazione ad albero di un elemento in Cosmos DB.

Diagramma ad albero che mostra un nodo radice con tre rami: "locations", "headquarters" e "export". "Locations" si divide in due nodi numerati, ognuno con due sottonodi correlati alla posizione ("Germania/Berlino" e "Francia/Parigi"). "Quartier generale" ha "Belgio" per la sua posizione e "dipendenti" ("250"). "Esportazioni" si divide in due nodi numerati, ognuno con un sottonodo "città" ("Mosca" e "Atene"). Il percorso per "località", "1", "location" e "France" è evidenziato.

Visto che questa query ha un filtro di uguaglianza, dopo aver attraversato questa struttura ad albero, possiamo identificare rapidamente le pagine dell’indice che contengono i risultati della query. In questo caso, il motore di query leggerà le pagine dell’indice che contengono l'elemento 1. Una ricerca dell’indice è il modo più efficiente per usare l'indice. Con una ricerca dell’indice, leggiamo solo le pagine dell’indice necessarie e carichiamo solo gli elementi nei risultati della query. Di conseguenza, il tempo di ricerca dell'indice e l'addebito delle UR dalla ricerca dell'indice sono incredibilmente bassi, indipendentemente dal volume di dati totale.

Analisi precisa dell'indice

Si consideri la seguente interrogazione:

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

Il predicato di query (filtrando gli elementi in cui sono presenti più di 200 dipendenti) può essere valutato con un'analisi precisa dell'indice del percorso headquarters/employees. Facendo un'analisi dell’indice precisa, il motore di query inizia eseguendo una ricerca binaria del set distinto dei possibili valori per trovare la posizione del valore 200 per il percorso headquarters/employees. Visto che i valori per ogni percorso sono in ordine crescente, è facile per il motore di query eseguire una ricerca binaria. Dopo che il motore di query trova il valore 200, inizia a leggere tutte le pagine dell’indice rimanenti (in maniera crescente).

Dato che il motore di query può eseguire una ricerca binaria per evitare l'analisi di pagine dell’indice non necessarie, le analisi precise degli indici tendono ad avere una latenza simile e gli addebiti UR per le operazioni di ricerca degli indici.

Analisi estesa dell'indice

Si consideri la seguente interrogazione:

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

Il predicato di query (filtraggio degli elementi con sede in una località che inizia con "United" senza distinzione tra maiuscole e minuscole) può essere valutato con una scansione dell'indice espansa del percorso headquarters/country. Le operazioni che eseguono un'analisi estesa dell’indice hanno ottimizzazioni che aiutano a evitare di dover analizzare ogni pagina dell’indice, ma sono leggermente più care rispetto ad una ricerca binaria di un'analisi dell'indice precisa.

Ad esempio, quando si valuta la distinzione tra maiuscole e minuscole StartsWith, il motore di query controlla l'indice cercando diverse combinazioni possibili di valori con maiuscole e minuscole. Questa ottimizzazione consente al motore di query di evitare di leggere la maggior parte delle pagine dell’indice. Diverse funzioni di sistema hanno svariate ottimizzazioni che possono usare per evitare di leggere ogni pagina dell’indice, per poi categorizzarle in maniera generale come analisi ampia dell'indice.

Analisi completa dell'indice

Si consideri la seguente interrogazione:

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

Il predicato di query (filtrando gli elementi che sono in una posizione che contiene "United") può essere valutato con un'analisi dell'indice del percorso headquarters/country. A differenza di un'analisi di indice precisa, un'analisi completa dell'indice analizza sempre il set distinto dei valori possibili per identificare le pagine dell’indice in cui sono presenti dei risultati. In questo caso, CONTAINS viene eseguito sull'indice. Il tempo di ricerca dell'indice e l'addebito UR per le analisi degli indici aumenta man mano che aumenta la cardinalità del percorso. In altre parole, sono i valori distinti possibili che il motore di query deve analizzare, maggiore è la latenza e l'addebito UR coinvolti nell'esecuzione di un'analisi completa dell'indice.

Prendiamo, per esempio, due proprietà: town e country. La cardinalità della città è 5.000 e la cardinalità di country è 200. Di seguito sono riportate due query di esempio ognuna con una CONTAINS funzione di sistema che esegue un'analisi completa dell'indice sulla town proprietà . La prima query utilizza più unità di richiesta (UR) rispetto alla seconda query perché la cardinalità della località è maggiore di country.

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

Analisi completa

In alcuni casi, il motore di query potrebbe non essere in grado di valutare un filtro di query usando l'indice. In questo caso, il motore di query deve caricare tutti gli elementi dall'archivio transazionale per valutare il filtro di query. Le analisi complete non usano l'indice e hanno un addebito di UR che aumenta in modo lineare rispetto alle dimensioni totali dei dati. Fortunatamente, le operazioni che richiedono analisi complete sono rare.

Query di ricerca vettoriali senza un indice vettoriale definito

Se non si definisce un criterio di indice vettoriale e si usa la funzione di sistema VECTORDISTANCE in una clausola ORDER BY, questa query restituisce una scansione completa e ha un addebito RU superiore rispetto a se è stato definito un criterio di indice vettoriale. Somiglianza, se si usa VECTORDISTANCE con il valore booleano di forza bruta impostato su true e non si dispone di un flat indice definito per il percorso vettoriale, viene eseguita un'analisi completa.

Query con espressioni di filtro complesse

Negli esempi precedenti abbiamo visto solo query con espressioni di filtro semplici (ad esempio query con un solo filtro di uguaglianza o di range). In realtà, la maggior parte delle query ha espressioni di filtro molto più complesse.

Si consideri la seguente interrogazione:

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

Per eseguire questa query, il motore di query deve fare una ricerca dell’indice su headquarters/employees e l'analisi completa dell'indice su headquarters/country. Il motore di query ha un’euristica interna usata per valutare l'espressione di filtro delle query nel modo più efficiente possibile. In questo caso, il motore di query eviterebbe di leggere le pagine dell’indice non necessarie eseguendo prima la ricerca dell'indice. Se, ad esempio, solo 50 elementi corrispondono al filtro di uguaglianza, il motore di query dovrà valutare solo CONTAINS nelle pagine dell’indice che contengono questi 50 elementi. Non sarebbe necessaria un'analisi completa dell'indice dell'intero contenitore.

Utilizzo degli indici per le funzioni di aggregazione scalari

Le query con funzioni di aggregazione devono basarsi esclusivamente sull'indice per usarle.

In alcuni casi, l'indice può restituire dei falsi positivi. Ad esempio, quando si valuta CONTAINS sull'indice, il numero di corrispondenze nell'indice potrebbe superare il numero di risultati della query. Il motore di query carica tutte le corrispondenze dell’indice, valuta il filtro sugli elementi caricati e restituisce solo i risultati corretti.

Per la maggior parte delle query, il caricamento dei falsi positivi dell’indice non ha alcun effetto rilevante sull'utilizzo dell'indice.

Ad esempio, si consideri la query seguente:

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

La CONTAINS funzione di sistema potrebbe restituire alcune corrispondenze false positive, pertanto il motore di query deve verificare se ogni elemento caricato corrisponde all'espressione di filtro. In questo esempio, il motore di query potrebbe dover caricare solo alcuni elementi aggiuntivi, quindi l'effetto sull'utilizzo dell'indice e l'addebito delle UR è minimo.

Tuttavia, le query con funzioni di aggregazione devono basarsi esclusivamente sull'indice per usarle. Si consideri ad esempio la query seguente con un'aggregazione COUNT:

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

Come nel primo esempio, la funzione sistema CONTAINS potrebbe restituire alcune corrispondenze false positive. Tuttavia, a differenza della query SELECT *, la query COUNT non può valutare l'espressione di filtro sugli elementi caricati per verificare tutte le corrispondenze di indice. La query COUNT deve basarsi esclusivamente sull'indice, quindi se un'espressione di filtro può restituire dei falsi positivi, il motore di query esegue a un'analisi completa.

Le query con le funzioni di aggregazione seguenti devono basarsi esclusivamente sull'indice, quindi la valutazione di alcune funzioni di sistema richiede un'analisi completa.