Descrição geral da indexação no Azure Cosmos DB

APLICA-SE A: NoSQL MongoDB Cassandra Gremlin Tabela

O Azure Cosmos DB é uma base de dados agnóstica de esquema que lhe permite iterar na sua aplicação sem ter de lidar com a gestão de esquemas ou índices. Por predefinição, o Azure Cosmos DB indexa automaticamente todas as propriedades de todos os itens no contentor sem ter de definir qualquer esquema ou configurar índices secundários.

O objetivo deste artigo é explicar como o Azure Cosmos DB indexa os dados e como utiliza os índices para melhorar o desempenho da consulta. Recomenda-se que percorra esta secção antes de explorar como personalizar políticas de indexação.

De itens a árvores

Sempre que um item é armazenado num contentor, o respetivo conteúdo é projetado como um documento JSON e, em seguida, convertido numa representação de árvore. Esta conversão significa que todas as propriedades desse item são representadas como um nó numa árvore. Um pseudo nó raiz é criado como elemento principal para todas as propriedades de primeiro nível do item. Os nós de folha contêm os valores escalares reais transportados por um item.

Por exemplo, considere este item:

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

Esta árvore representa o item JSON de exemplo:

Diagrama do item JSON anterior representado como uma árvore.

Tenha em atenção como as matrizes são codificadas na árvore: cada entrada numa matriz obtém um nó intermédio etiquetado com o índice dessa entrada na matriz (0, 1, etc.).

De árvores a caminhos de propriedade

A razão pela qual o Azure Cosmos DB transforma itens em árvores é porque permite ao sistema referenciar propriedades com os seus caminhos nessas árvores. Para obter o caminho de uma propriedade, podemos percorrer a árvore desde o nó de raiz até essa propriedade e concatenar as etiquetas de cada nó percorrido.

Eis os caminhos para cada propriedade do item de exemplo descrito anteriormente:

  • /locations/0/country: "Alemanha"
  • /locations/0/city: "Berlim"
  • /locations/1/country: "França"
  • /locations/1/city: "Paris"
  • /headquarters/country: "Bélgica"
  • /headquarters/employees: 250
  • /exports/0/city: "Moscovo"
  • /exports/1/city: "Atenas"

O Azure Cosmos DB indexa efetivamente o caminho de cada propriedade e o respetivo valor correspondente quando um item é escrito.

Tipos de índices

Atualmente, o Azure Cosmos DB suporta três tipos de índices. Pode configurar estes tipos de índice ao definir a política de indexação.

Índice de Intervalos

Os índices de intervalo baseiam-se numa estrutura ordenada semelhante a uma árvore. O tipo de índice de intervalo é utilizado para:

  • Consultas de igualdade:

    SELECT * FROM container c WHERE c.property = 'value'
    
    SELECT * FROM c WHERE c.property IN ("value1", "value2", "value3")
    
  • Correspondência de igualdade num elemento de matriz

    SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, "tag1")
    
  • Consultas de intervalo:

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

    Nota

    (funciona para >, <, >=, <=, !=)

  • Verificar a presença de uma propriedade:

    SELECT * FROM c WHERE IS_DEFINED(c.property)
    
  • Funções do sistema de cadeias:

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

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

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

Os índices de intervalo podem ser utilizados em valores escalares (cadeia ou número). A política de indexação predefinida para os contentores recém-criados impõe índices de intervalo para qualquer cadeia ou número. Para saber como configurar índices de intervalos, veja Exemplos de políticas de indexação de intervalos

Nota

Uma ORDER BY cláusula que ordena por uma única propriedade precisa sempre de um índice de intervalo e falhará se o caminho que referencia não tiver um. Da mesma forma, uma ORDER BY consulta que ordena por múltiplas propriedades precisa sempre de um índice composto.

Índice espacial

Os índices espaciais permitem consultas eficientes em objetos geoespaciais, como pontos, linhas, polígonos e multipólgonos. Estas consultas utilizam palavras-chave ST_DISTANCE, ST_WITHIN ST_INTERSECTS. Seguem-se alguns exemplos que utilizam o tipo de índice espacial:

  • Consultas de distância geoespacial:

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

    SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
    
  • Consultas de interseção geoespacial:

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

Os índices espaciais podem ser utilizados em objetos GeoJSON corretamente formatados. Pontos, LineStrings, Polígonos e Multipolygons são atualmente suportados. Para saber como configurar índices espaciais, veja Exemplos de políticas de indexação espacial

Índices compostos

Os índices compostos aumentam a eficiência quando executa operações em vários campos. O tipo de índice composto é utilizado para:

  • ORDER BY consultas em várias propriedades:

    SELECT * FROM container c ORDER BY c.property1, c.property2
    
  • Consultas com um filtro e ORDER BY. Estas consultas podem utilizar um índice composto se a propriedade do filtro for adicionada à ORDER BY cláusula .

    SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2
    
  • Consultas com um filtro em duas ou mais propriedades em que pelo menos uma propriedade é um filtro de igualdade

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

Desde que um predicado de filtro utilize um dos tipos de índice, o motor de consulta avalia-o primeiro antes de analisar o resto. Por exemplo, se tiver uma consulta SQL, como SELECT * FROM c WHERE c.firstName = "Andrew" and CONTAINS(c.lastName, "Liu")

  • A consulta acima filtrará primeiro as entradas em que firstName = "Andrew" utilizando o índice. Em seguida, transmite todas as entradas firstName = "Andrew" através de um pipeline subsequente para avaliar o predicado de filtro CONTAINS.

  • Pode acelerar as consultas e evitar análises completas de contentores ao utilizar funções que efetuam uma análise completa, como CONTAINS. Pode adicionar mais predicados de filtro que utilizam o índice para acelerar estas consultas. A ordem das cláusulas de filtro não é importante. O motor de consulta descobre que predicados são mais seletivos e executa a consulta em conformidade.

Para saber como configurar índices compostos, veja Exemplos de políticas de indexação composta

Utilização do índice

Existem cinco formas de o motor de consulta avaliar os filtros de consulta, ordenados pelo mais eficiente para o menos eficiente:

  • Procura de índice
  • Análise de índice precisa
  • Análise de índice expandida
  • Análise de índice completa
  • Análise completa

Quando indexa caminhos de propriedade, o motor de consulta utiliza automaticamente o índice da forma mais eficiente possível. Além de indexar novos caminhos de propriedade, não precisa de configurar nada para otimizar a forma como as consultas utilizam o índice. O custo de RUs de uma consulta é uma combinação do custo de RUs da utilização do índice e do custo de RUs do carregamento de itens.

Eis uma tabela que resume as diferentes formas como os índices são utilizados no Azure Cosmos DB:

Tipo de pesquisa de índice Description Exemplos Comuns Custo de RU da utilização do índice Custos de RU ao carregar itens do arquivo de dados transacional
Procura de índice Ler apenas os valores indexados necessários e carregar apenas itens correspondentes do arquivo de dados transacional Filtros de igualdade, IN Filtro constante por igualdade Aumentos com base no número de itens nos resultados da consulta
Análise precisa do índice Pesquisa binária de valores indexados e carregar apenas itens correspondentes do arquivo de dados transacional Comparações de intervalos (>, <, <=ou >=), StartsWith Comparável à procura de índices, aumenta ligeiramente com base na cardinalidade das propriedades indexadas Aumentos com base no número de itens nos resultados da consulta
Análise de índice expandida Pesquisa otimizada (mas menos eficiente do que uma pesquisa binária) de valores indexados e carregar apenas itens correspondentes do arquivo de dados transacional StartsWith (não sensível a maiúsculas e minúsculas), StringEquals (não sensível a maiúsculas e minúsculas) Aumenta ligeiramente com base na cardinalidade das propriedades indexadas Aumentos com base no número de itens nos resultados da consulta
Análise completa do índice Ler um conjunto distinto de valores indexados e carregar apenas itens correspondentes do arquivo de dados transacional Contém, EndsWith, RegexMatch, LIKE Aumenta linearmente com base na cardinalidade das propriedades indexadas Aumentos com base no número de itens nos resultados da consulta
Análise completa Carregar todos os itens a partir do arquivo de dados transacional Superior, Inferior N/D Aumenta com base no número de itens no contentor

Ao escrever consultas, deve utilizar predicados de filtro que utilizem o índice da forma mais eficiente possível. Por exemplo, se funcionar StartsWith ou Contains funcionar para o seu caso de utilização, deve optar por StartsWith , uma vez que faz uma análise de índice precisa em vez de uma análise de índice completa.

Detalhes de utilização do índice

Nesta secção, vamos abordar mais detalhes sobre como as consultas utilizam índices. Este nível de detalhe não é necessário para aprender a começar a utilizar o Azure Cosmos DB, mas está documentado em detalhe para utilizadores curiosos. Referimos o item de exemplo partilhado anteriormente neste documento:

Itens de exemplo:

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

O Azure Cosmos DB utiliza um índice invertido. O índice funciona ao mapear cada caminho JSON para o conjunto de itens que contêm esse valor. O mapeamento do ID do item é representado em muitas páginas de índice diferentes para o contentor. Segue-se um diagrama de exemplo de um índice invertido para um contentor que inclui os dois itens de exemplo:

Caminho Valor Lista de IDs de itens
/locations/0/country Alemanha 1
/locations/0/country Irlanda 2
/locations/0/city Berlim 1
/locations/0/city Dublin 2
/locations/1/country França 1
/locations/1/city Paris 1
/sede/país Bélgica 1, 2
/sede/funcionários 200 2
/sede/funcionários 250 1

O índice invertido tem dois atributos importantes:

  • Para um determinado caminho, os valores são ordenados por ordem ascendente. Por conseguinte, o motor de consulta pode servir ORDER BY facilmente a partir do índice.
  • Para um determinado caminho, o motor de consulta pode analisar o conjunto distinto de valores possíveis para identificar as páginas de índice onde existem resultados.

O motor de consulta pode utilizar o índice invertido de quatro formas diferentes:

Procura de índice

Considere a consulta seguinte:

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

O predicado de consulta (filtrar itens em que qualquer localização tenha "França" como país/região) corresponderia ao caminho realçado neste diagrama:

Diagrama de um percurso (pesquisa) que corresponde a um caminho específico dentro de uma árvore.

Uma vez que esta consulta tem um filtro de igualdade, depois de percorrer esta árvore, podemos identificar rapidamente as páginas de índice que contêm os resultados da consulta. Neste caso, o motor de consulta leria páginas de índice que contêm o Item 1. Uma procura de índice é a forma mais eficiente de utilizar o índice. Com uma procura de índice, só lemos as páginas de índice necessárias e carregamos apenas os itens nos resultados da consulta. Por conseguinte, o tempo de pesquisa do índice e o custo de RU da pesquisa de índice são incrivelmente baixos, independentemente do volume total de dados.

Análise precisa do índice

Considere a consulta seguinte:

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

O predicado de consulta (filtrar itens em que existem mais de 200 funcionários) pode ser avaliado com uma análise de índice precisa do headquarters/employees caminho. Ao fazer uma análise de índice precisa, o motor de consulta começa por fazer uma pesquisa binária do conjunto distinto de valores possíveis para encontrar a localização do valor 200 para o headquarters/employees caminho. Uma vez que os valores de cada caminho são ordenados por ordem ascendente, é fácil para o motor de consulta fazer uma pesquisa binária. Depois de o motor de consulta encontrar o valor 200, começa a ler todas as páginas de índice restantes (indo na direção ascendente).

Uma vez que o motor de consulta pode fazer uma pesquisa binária para evitar a análise de páginas de índice desnecessárias, as análises de índices precisas tendem a ter latência comparável e custos de RU para indexar operações de procura.

Análise de índice expandida

Considere a consulta seguinte:

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

O predicado de consulta (filtrar itens que têm sede numa localização que comece com "United") não sensível a maiúsculas e minúsculas pode ser avaliado com uma análise de índice expandida do headquarters/country caminho. As operações que fazem uma análise de índice expandida têm otimizações que podem ajudar a evitar necessidades de analisar todas as páginas de índice, mas são ligeiramente mais caras do que a pesquisa binária de uma análise de índice precisa.

Por exemplo, ao avaliar valores insensíveis StartsWitha maiúsculas e minúsculas, o motor de consulta verifica o índice para obter diferentes combinações possíveis de valores em maiúsculas e minúsculas. Esta otimização permite que o motor de consulta evite ler a maioria das páginas de índice. Diferentes funções do sistema têm otimizações diferentes que podem utilizar para evitar a leitura de todas as páginas de índice, pelo que são amplamente categorizadas como análise de índice expandida.

Análise completa do índice

Considere a consulta seguinte:

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

O predicado de consulta (filtrar itens que têm a sede numa localização que contém "United") pode ser avaliado com uma análise de índice do headquarters/country caminho. Ao contrário de uma análise de índice precisa, uma análise de índice completa analisa sempre o conjunto distinto de valores possíveis para identificar as páginas de índice onde existem resultados. Neste caso, Contains é executado no índice. O tempo de pesquisa do índice e a taxa de RU das análises de índice aumentam à medida que a cardinalidade do caminho aumenta. Por outras palavras, quanto mais possíveis valores distintos o motor de consulta precisar de analisar, maior é a latência e a carga de RU envolvidas na realização de uma análise de índice completa.

Por exemplo, considere duas propriedades: town e country. A cardinalidade da cidade é de 5.000 e a cardinalidade de é de country 200. Eis duas consultas de exemplo que cada uma tem uma função Contém o sistema que faz uma análise de índice completa na town propriedade. A primeira consulta utiliza mais RUs do que a segunda consulta porque a cardinalidade da cidade é superior countrya .

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

Análise completa

Em alguns casos, o motor de consulta pode não conseguir avaliar um filtro de consulta com o índice. Neste caso, o motor de consulta tem de carregar todos os itens do arquivo transacional para avaliar o filtro de consulta. As análises completas não utilizam o índice e têm uma carga de RU que aumenta linearmente com o tamanho total dos dados. Felizmente, as operações que requerem análises completas são raras.

Consultas com expressões de filtro complexas

Nos exemplos anteriores, apenas considerámos consultas que tinham expressões de filtro simples (por exemplo, consultas com apenas um único filtro de igualdade ou intervalo). Na realidade, a maioria das consultas tem expressões de filtro muito mais complexas.

Considere a consulta seguinte:

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

Para executar esta consulta, o motor de consulta tem de efetuar uma pesquisa de índice e headquarters/employees uma análise de índice completa em headquarters/country. O motor de consulta tem heurística interna que utiliza para avaliar a expressão do filtro de consulta da forma mais eficiente possível. Neste caso, o motor de consulta evitaria a necessidade de ler páginas de índice desnecessárias ao procurar primeiro o índice. Se, por exemplo, apenas 50 itens corresponderem ao filtro de igualdade, o motor de consulta só terá de avaliar Contains nas páginas de índice que continham esses 50 itens. Não seria necessária uma análise de índice completa de todo o contentor.

Utilização do índice para funções de agregação escalar

As consultas com funções de agregação têm de depender exclusivamente do índice para poder utilizá-lo.

Em alguns casos, o índice pode devolver falsos positivos. Por exemplo, ao avaliar Contains no índice, o número de correspondências no índice pode exceder o número de resultados da consulta. O motor de consulta carrega todas as correspondências de índice, avalia o filtro nos itens carregados e devolve apenas os resultados corretos.

Para a maioria das consultas, carregar correspondências de índices falsos positivos não tem qualquer efeito notável na utilização do índice.

Por exemplo, considere a consulta seguinte:

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

A função do Contains sistema pode devolver algumas correspondências de falsos positivos, pelo que o motor de consulta tem de verificar se cada item carregado corresponde à expressão de filtro. Neste exemplo, o motor de consulta só poderá ter de carregar mais alguns itens, pelo que o efeito na utilização do índice e na carga de RU é mínimo.

No entanto, as consultas com funções de agregação têm de depender exclusivamente do índice para o utilizar. Por exemplo, considere a seguinte consulta com uma Count agregação:

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

Tal como no primeiro exemplo, a função do Contains sistema pode devolver algumas correspondências de falsos positivos. No entanto, ao contrário da SELECT * consulta, a Count consulta não consegue avaliar a expressão de filtro nos itens carregados para verificar todas as correspondências de índice. A Count consulta tem de depender exclusivamente do índice, pelo que, se existir uma hipótese de uma expressão de filtro devolver correspondências falsas positivas, o motor de consulta recorre a uma análise completa.

As consultas com as seguintes funções de agregação têm de depender exclusivamente do índice, pelo que avaliar algumas funções do sistema requer uma análise completa.

Passos seguintes

Leia mais sobre a indexação nos seguintes artigos: