Resolver problemas de consultas ao utilizar o Azure Cosmos DB
APLICA-SE A: NoSQL
Este artigo apresenta uma abordagem geral recomendada para a resolução de problemas de consultas no Azure Cosmos DB. Embora você não deva considerar as etapas descritas neste artigo uma defesa completa contra possíveis problemas de consulta, incluímos as dicas de desempenho mais comuns aqui. Você deve usar este artigo como um ponto de partida para solucionar problemas de consultas lentas ou caras no Azure Cosmos DB para NoSQL. Também pode utilizar os registos de diagnóstico para identificar consultas que são lentas ou que consomem um débito significativo. Se você estiver usando a API do Azure Cosmos DB para MongoDB, deverá usar o guia de solução de problemas de consulta da API do Azure Cosmos DB para MongoDB
As otimizações de consulta no Azure Cosmos DB são amplamente categorizadas da seguinte forma:
- Otimizações que reduzem a cobrança da unidade de solicitação (RU) da consulta
- Otimizações que apenas reduzem a latência
Se você reduzir a cobrança de RU de uma consulta, normalmente também diminuirá a latência.
Problemas comuns do SDK
Antes de ler este guia, é útil considerar problemas comuns do SDK que não estão relacionados ao mecanismo de consulta.
- Siga estas dicas de desempenho do SDK para consulta.
- Por vezes, as consultas podem ter páginas vazias mesmo quando há resultados numa página futura. As razões para tal podem ser:
- O SDK pode estar fazendo várias chamadas de rede.
- A consulta pode estar demorando muito para recuperar os documentos.
- Todas as consultas têm um token de continuação que permite que a consulta continue. Esvazie completamente a consulta. Saiba mais sobre como lidar com várias páginas de resultados
Obter métricas da consulta
Quando você otimiza uma consulta no Azure Cosmos DB, a primeira etapa é sempre obter as métricas de consulta para sua consulta. Essas métricas também estão disponíveis por meio do portal do Azure. Depois de executar a consulta no Data Explorer, as métricas de consulta ficam visíveis ao lado da guia Resultados :
Depois de obter as métricas da consulta, compare a Contagem de Documentos Obtidos com a Contagem de Documentos de Saída da sua consulta. Utilize essa comparação para identificar as secções relevantes que devem ser lidas neste artigo.
A Contagem de Documentos Recuperados é o número de documentos que o mecanismo de consulta precisava carregar. A Contagem de Documentos de Saída é o número de documentos necessários para os resultados da consulta. Se a Contagem de Documentos Recuperados for maior do que a Contagem de Documentos de Saída, houve pelo menos uma parte da sua consulta que não pôde usar um índice e precisou fazer uma verificação.
Consulte as seções a seguir para entender as otimizações de consulta relevantes para seu cenário.
A taxa de RU do Query é muito alta
A Contagem de Documentos Recuperados é maior do que a Contagem de Documentos de Saída
A Contagem de Documentos Recuperados é aproximadamente igual à Contagem de Documentos de Saída
A cobrança de RU do Query é aceitável, mas a latência ainda é muito alta
Consultas em que a Contagem de Documentos Recuperados excede a Contagem de Documentos de Saída
A Contagem de Documentos Recuperados é o número de documentos que o mecanismo de consulta precisava carregar. A Contagem de Documentos de Saída é o número de documentos retornados pela consulta. Se a Contagem de Documentos Recuperados for maior do que a Contagem de Documentos de Saída, houve pelo menos uma parte da sua consulta que não pôde usar um índice e precisou fazer uma verificação.
Aqui está um exemplo de consulta de verificação que não foi totalmente atendida pelo índice:
Consulta:
SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"
Métricas de consulta:
Retrieved Document Count : 60,951
Retrieved Document Size : 399,998,938 bytes
Output Document Count : 7
Output Document Size : 510 bytes
Index Utilization : 0.00 %
Total Query Execution Time : 4,500.34 milliseconds
Query Preparation Times
Query Compilation Time : 0.09 milliseconds
Logical Plan Build Time : 0.05 milliseconds
Physical Plan Build Time : 0.04 milliseconds
Query Optimization Time : 0.01 milliseconds
Index Lookup Time : 0.01 milliseconds
Document Load Time : 4,177.66 milliseconds
Runtime Execution Times
Query Engine Times : 322.16 milliseconds
System Function Execution Time : 85.74 milliseconds
User-defined Function Execution Time : 0.00 milliseconds
Document Write Time : 0.01 milliseconds
Client Side Metrics
Retry Count : 0
Request Charge : 4,059.95 RUs
A Contagem de Documentos Recuperados (60.951) é maior do que a Contagem de Documentos de Saída (7), o que implica que esta consulta resultou numa digitalização de documentos. Nesse caso, a função do sistema UPPER() não usa um índice.
Incluir os caminhos necessários na política de indexação
Sua política de indexação deve abranger todas as propriedades incluídas em WHERE
cláusulas ORDER BY
, JOIN
cláusulas e a maioria das funções do sistema. Os caminhos desejados especificados na política de indexação devem corresponder às propriedades nos documentos JSON.
Nota
As propriedades na política de indexação do Azure Cosmos DB diferenciam maiúsculas de minúsculas
Original
Consulta:
SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"
Política de Indexação:
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": [
{
"path": "/description/*"
}
]
}
Custo das RUs: 409,51 RUs
Otimizado
Política de indexação atualizada:
{
"indexingMode": "consistent",
"automatic": true,
"includedPaths": [
{
"path": "/*"
}
],
"excludedPaths": []
}
Custo das RUs: 2,98 RUs
Você pode adicionar propriedades à política de indexação a qualquer momento, sem efeito na disponibilidade de gravação ou leitura. Você pode acompanhar o progresso da transformação do índice.
Compreender que funções do sistema utilizam o índice
A maioria das funções do sistema usa índices. Aqui está uma lista de algumas funções de cadeia de caracteres comuns que usam índices:
- StartsWith
- Contains
- RegexMatch
- Left
- Substring - mas somente se o primeiro num_expr for 0
A seguir estão algumas funções comuns do sistema que não usam o índice e devem carregar cada documento quando usado em uma WHERE
cláusula:
Função do sistema | Ideias para otimização |
---|---|
Superior/Inferior | Em vez de usar a função do sistema para normalizar dados para comparações, normalize o invólucro após a inserção. Uma consulta como SELECT * FROM c WHERE UPPER(c.name) = 'BOB' torna-se SELECT * FROM c WHERE c.name = 'BOB' . |
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks | Calcule o tempo atual antes da execução da consulta e use esse valor de WHERE cadeia de caracteres na cláusula. |
Funções matemáticas (não agregadas) | Se você precisar calcular um valor com freqüência em sua consulta, considere armazenar o valor como uma propriedade em seu documento JSON. |
Essas funções do sistema podem usar índices, exceto quando usadas em consultas com agregados:
Função do sistema | Ideias para otimização |
---|---|
Funções do sistema espacial | Armazene o resultado da consulta em uma exibição materializada em tempo real |
Quando usadas na cláusula, funções ineficientes do SELECT
sistema não afetarão como as consultas podem usar índices.
Melhorar a execução da função do sistema de cadeias
Para algumas funções do sistema que usam índices, você pode melhorar a execução da consulta adicionando uma ORDER BY
cláusula à consulta.
Mais especificamente, qualquer função do sistema cuja carga de RU aumenta à medida que a cardinalidade da propriedade aumenta pode se beneficiar de ter ORDER BY
na consulta. Essas consultas fazem uma verificação de índice, portanto, ter os resultados da consulta classificados pode tornar a consulta mais eficiente.
Esta otimização pode melhorar a execução das seguintes funções do sistema:
- StartsWith (onde insensível a maiúsculas e minúsculas = true)
- StringEquals (onde insensível a maiúsculas e minúsculas = true)
- Contains
- RegexMatch
- EndsWith
Por exemplo, considere a consulta abaixo com CONTAINS
. CONTAINS
usará índices, mas às vezes, mesmo depois de adicionar o índice relevante, você ainda pode observar uma alta cobrança de RU ao executar a consulta abaixo.
Consulta original:
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
Você pode melhorar a execução da consulta adicionando ORDER BY
:
SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town
A mesma otimização pode ajudar em consultas com outros filtros. Nesse caso, é melhor também adicionar propriedades com filtros de igualdade à ORDER BY
cláusula.
Consulta original:
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
Você pode melhorar a execução da consulta adicionando ORDER BY
um índice composto para (c.name, c.town):
SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town
Compreender que consultas agregadas utilizam o índice
Na maioria dos casos, as funções agregadas do sistema no Azure Cosmos DB usam o índice. No entanto, dependendo dos filtros ou de outras cláusulas em uma consulta agregada, o mecanismo de consulta pode ser necessário para carregar um grande número de documentos. Normalmente, o mecanismo de consulta aplica filtros de igualdade e intervalo primeiro. Depois de aplicar esses filtros, o mecanismo de consulta pode avaliar outros filtros e recorrer ao carregamento de documentos restantes para calcular a agregação, se necessário.
Por exemplo, dadas essas duas consultas de exemplo, a consulta com um filtro de igualdade e CONTAINS
função do sistema é geralmente mais eficiente do que uma consulta com apenas um filtro de CONTAINS
função do sistema. Isso ocorre porque o filtro de igualdade é aplicado primeiro e usa o índice antes que os documentos precisem ser carregados para o filtro mais caro CONTAINS
.
Consulta apenas com CONTAINS
filtro - maior carga de RU:
SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")
Consulta com filtro de igualdade e CONTAINS
filtro - menor carga de RU:
SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")
Aqui estão mais exemplos de consultas agregadas que não usarão totalmente o índice:
Consultas com funções do sistema que não usam o índice
Você deve consultar a página da função do sistema relevante para ver se ela usa o índice.
SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")
Agregar consultas com funções definidas pelo usuário (UDF's)
SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")
Consultas com GROUP BY
A cobrança de RU de consultas com GROUP BY
aumento à medida que a cardinalidade dos imóveis na GROUP BY
cláusula aumenta. Na consulta abaixo, por exemplo, a cobrança de RU da consulta aumenta à medida que o número de descrições exclusivas aumenta.
A carga RU de uma função agregada com uma GROUP BY
cláusula é maior do que a carga RU de uma função agregada sozinha. Neste exemplo, o mecanismo de consulta deve carregar todos os documentos que correspondem ao c.foodGroup = "Sausages and Luncheon Meats"
filtro para que a carga de RU seja alta.
SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description
Se você planeja executar frequentemente as mesmas consultas agregadas, pode ser mais eficiente criar uma exibição materializada em tempo real com o feed de alterações do Azure Cosmos DB do que executar consultas individuais.
Otimizar consultas que tenham um filtro e uma cláusula ORDER BY
Embora as consultas que têm um filtro e uma ORDER BY
cláusula normalmente usem um índice de intervalo, elas são mais eficientes se puderem ser atendidas a partir de um índice composto. Além de modificar a política de indexação, você deve adicionar todas as propriedades no índice composto à ORDER BY
cláusula. Essa alteração na consulta garante que ela use o índice composto.
Original
Consulta:
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC
Política de Indexação:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[]
}
Carga RU: 44,28 RUs
Otimizado
Consulta atualizada (inclui ambas as propriedades na ORDER BY
cláusula):
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC
Política de indexação atualizada:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Carga RU: 8,86 RUs
Otimizar expressões JOIN com uma subconsulta
As subconsultas de vários valores podem otimizar JOIN
expressões empurrando predicados após cada expressão select-many, em vez de depois de todas as junções cruzadas na WHERE
cláusula.
Considere esta consulta:
SELECT Count(1) AS Count
FROM c
JOIN t IN c.tags
JOIN n IN c.nutrients
JOIN s IN c.servings
WHERE t.name = 'infant formula' AND (n.nutritionValue > 0
AND n.nutritionValue < 10) AND s.amount > 1
Carga RU: 167,62 RUs
Para esta consulta, o índice corresponde a qualquer documento que tenha uma marca com o nome infant formula
, nutritionValue
maior que 0 e amount
maior que 1. A JOIN
expressão aqui executa o produto cruzado de todos os itens de tags, nutrientes e matrizes de porções para cada documento correspondente antes que qualquer filtro seja aplicado. A WHERE
cláusula aplicará então o predicado do filtro em cada <c, t, n, s>
tupla.
Por exemplo, se um documento correspondente tiver 10 itens em cada uma das três matrizes, ele se expandirá para 1 x 10 x 10 x 10 (ou seja, 1.000) tuplas. O uso de subconsultas aqui pode ajudar a filtrar itens de matriz associados antes de ingressar com a próxima expressão.
Esta consulta é equivalente à anterior, mas usa subconsultas:
SELECT Count(1) AS Count
FROM c
JOIN (SELECT VALUE t FROM t IN c.tags WHERE t.name = 'infant formula')
JOIN (SELECT VALUE n FROM n IN c.nutrients WHERE n.nutritionValue > 0 AND n.nutritionValue < 10)
JOIN (SELECT VALUE s FROM s IN c.servings WHERE s.amount > 1)
Carga RU: 22,17 RUs
Suponha que apenas um item na matriz de tags corresponde ao filtro e que há cinco itens para as matrizes de nutrientes e porções. As JOIN
expressões se expandem para 1 x 1 x 5 x 5 = 25 itens, em oposição a 1.000 itens na primeira consulta.
Consultas em que a Contagem de Documentos Obtidos é igual à Contagem de Documentos Produzidos
Se a Contagem de Documentos Obtidos for aproximadamente igual à Contagem de Documentos Produzidos, o motor de consulta não terá de analisar muitos documentos desnecessários. Para muitas consultas, como aquelas que usam a palavra-chave, a Contagem de Documentos Recuperados pode exceder a TOP
Contagem de Documentos de Saída em 1. Não precisa de se preocupar com esta situação.
Minimizar as consultas entre partições
O Azure Cosmos DB usa o particionamento para dimensionar contêineres individuais à medida que a Unidade de Solicitação e as necessidades de armazenamento de dados aumentam. Cada partição física tem um índice separado e independente. Se a sua consulta tiver um filtro de igualdade que corresponda à chave de partição do seu contêiner, você precisará verificar apenas o índice da partição relevante. Esta otimização reduz o número total de RUs necessárias para a consulta.
Se você tiver um grande número de RUs provisionadas (mais de 30.000) ou uma grande quantidade de dados armazenados (mais de aproximadamente 100 GB), provavelmente terá um contêiner grande o suficiente para ver uma redução significativa nas taxas de RU de consulta.
Por exemplo, se você criar um contêiner com a chave de partição foodGroup, as seguintes consultas precisarão verificar apenas uma única partição física:
SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"
As consultas que têm um IN
filtro com a chave de partição verificam apenas uma ou mais partições físicas relevantes e não "propagam-se":
SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"
As consultas que têm filtros de intervalo na chave de partição, ou que não têm filtros na chave de partição, precisarão "distribuir" e verificar o índice de cada partição física em busca de resultados:
SELECT *
FROM c
WHERE c.description = "Mushroom, oyster, raw"
SELECT *
FROM c
WHERE c.foodGroup > "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"
Otimizar consultas que têm filtros em várias propriedades
Embora as consultas que têm filtros em várias propriedades normalmente usem um índice de intervalo, elas são mais eficientes se puderem ser atendidas a partir de um índice composto. Para pequenas quantidades de dados, esta otimização não terá um impacto significativo. No entanto, pode ser útil para grandes quantidades de dados. Só pode otimizar, no máximo, um filtro de não igualdade por índice composto. Se a consulta tiver vários filtros de não igualdade, escolha um deles que utilizará o índice composto. O resto continua a usar índices de intervalo. O filtro de não-igualdade deve ser definido em último lugar no índice composto. Saiba mais sobre índices compostos.
Aqui estão alguns exemplos de consultas que podem ser otimizadas com um índice composto:
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts = 1575503264
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts > 1575503264
Aqui está o índice composto relevante:
{
"automatic":true,
"indexingMode":"Consistent",
"includedPaths":[
{
"path":"/*"
}
],
"excludedPaths":[],
"compositeIndexes":[
[
{
"path":"/foodGroup",
"order":"ascending"
},
{
"path":"/_ts",
"order":"ascending"
}
]
]
}
Otimizações que reduzem a latência da consulta
Em muitos casos, o custo de RU pode ser aceitável quando a latência da consulta ainda é demasiado elevada. As secções seguintes dão uma visão geral das sugestões para reduzir a latência das consultas. Se executar a mesma consulta várias vezes no mesmo conjunto de dados, terá, geralmente, o mesmo custo de RU de cada vez. Contudo, a latência das consultas pode variar entre as execuções das consultas.
Melhorar a proximidade
As consultas executadas de uma região diferente da conta do Azure Cosmos DB têm latência maior do que se fossem executadas dentro da mesma região. Por exemplo, se estiver a executar código no seu computador de secretária, deve esperar que a latência seja dezenas ou centenas de milissegundos mais elevada (ou mais) do que se a consulta viesse de uma máquina virtual na mesma região do Azure Cosmos DB. É simples distribuir dados globalmente no Azure Cosmos DB para garantir que você possa aproximar seus dados do seu aplicativo.
Aumentar a taxa de transferência provisionada
No Azure Cosmos DB, sua taxa de transferência provisionada é medida em Unidades de Solicitação (RUs). Imagine que tem uma consulta que consome 5 RUs de débito. Por exemplo, se aprovisionar 1000 RUs, poderá executar essa consulta 200 vezes por segundo. Se você tentasse executar a consulta quando não houvesse taxa de transferência suficiente disponível, o Azure Cosmos DB retornaria um erro HTTP 429. Qualquer uma das APIs atuais para SDKs NoSQL repetirá automaticamente essa consulta depois de aguardar por um curto período de tempo. Os pedidos com limitação demoram mais tempo, pelo que aumentar o débito aprovisionado pode melhorar a latência das consultas. Você pode observar o número total de solicitações limitadas na folha Métricas do portal do Azure.
Aumentar MaxConcurrency
As consultas paralelas funcionam consultando várias partições em paralelo. Mas os dados de uma coleção particionada individual são obtidos em série em relação à consulta. Assim, se você definir MaxConcurrency para o número de partições, você tem a melhor chance de alcançar a consulta de maior desempenho, desde que todas as outras condições do sistema permaneçam as mesmas. Se você não souber o número de partições, poderá definir MaxConcurrency (ou MaxDegreesOfParallelism em versões mais antigas do SDK) como um número alto. O sistema escolhe o mínimo (número de partições, entrada fornecida pelo usuário) como o grau máximo de paralelismo.
Aumentar MaxBufferedItemCount
As consultas são projetadas para pré-buscar resultados enquanto o lote atual de resultados está sendo processado pelo cliente. A pré-busca ajuda a melhorar a latência geral de uma consulta. A configuração de MaxBufferedItemCount limita o número de resultados pré-buscados. Se você definir esse valor para o número esperado de resultados retornados (ou um número maior), a consulta poderá obter o máximo benefício da pré-busca. Se você definir esse valor como -1, o sistema determinará automaticamente o número de itens a serem armazenados em buffer.
Próximos passos
Consulte os seguintes artigos para obter informações sobre como medir RUs por consulta, obter estatísticas de execução para ajustar suas consultas e muito mais: