Partilhar via


Resolver problemas de consultas ao utilizar o Azure Cosmos DB for MongoDB

APLICA-SE A: MongoDB

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 na API do Azure Cosmos DB para MongoDB. Se você estiver usando o Azure Cosmos DB para NoSQL, consulte o artigo do guia de solução de problemas de consulta da API para NoSQL.

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.

Este artigo fornece exemplos que você pode recriar usando o conjunto de dados de nutrição.

Nota

Este artigo pressupõe que você esteja usando a API do Azure Cosmos DB para contas MongoDB com versão 3.6 e superior. Algumas consultas que têm um desempenho fraco na versão 3.2 têm melhorias significativas nas versões 3.6+. Atualize para a versão 3.6 preenchendo uma solicitação de suporte.

Use $explain comando para obter métricas

Quando você otimiza uma consulta no Azure Cosmos DB, a primeira etapa é sempre obter a cobrança de RU para sua consulta. Como orientação geral, deve explorar formas de reduzir os custos de RU das consultas com custos superiores a 50 RUs.

Para além de obter o custo da RU, tem de utilizar o comando $explain para obter as métricas de utilização da consulta e do índice. Aqui está um exemplo que executa uma consulta e usa o comando para mostrar métricas de uso de $explain consulta e índice:

$explain comando:

db.coll.find({foodGroup: "Baby Foods"}).explain({"executionStatistics": true })

Saída:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 905.2888,
            "timeExclusiveMS" : 905.2888,
            "in" : 362,
            "out" : 362,
            "details" : {
                "database" : "db-test",
                "collection" : "collection-test",
                "query" : {
                    "foodGroup" : {
                        "$eq" : "Baby Foods"
                    }
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "e68e6bdd-5e89-4ec5-b053-3dbbc2428140",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 788.5867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 362,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 362,
                    "outputDocumentSizeBytes" : 2553535,
                    "indexHitRatio" : 0.0016802042237178,
                    "totalQueryExecutionTimeMS" : 777.72,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.19,
                        "logicalPlanBuildTimeMS" : 0.14,
                        "physicalPlanBuildTimeMS" : 0.09,
                        "queryOptimizationTimeMS" : 0.03
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 687.22,
                    "vmExecutionTimeMS" : 774.09,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 37.45,
                        "systemFunctionExecutionTimeMS" : 10.82,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 49.42
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

A $explain saída do comando é longa e tem informações detalhadas sobre a execução da consulta. Contudo, no geral, há algumas secções em que se deve concentrar quando otimizar o desempenho das consultas:

Métrico Description
timeInclusiveMS Latência da consulta de back-end
pathsIndexed Mostra os índices usados pela consulta
pathsNotIndexed Mostra os índices que a consulta poderia ter usado, se disponível
shardInformation Resumo do desempenho da consulta para uma partição física específica
retrievedDocumentCount Número de documentos carregados pelo mecanismo de consulta
outputDocumentCount Número de documentos retornados nos resultados da consulta
estimatedDelayFromRateLimitingInMilliseconds Latência de consulta adicional estimada devido à limitação da taxa

Depois de obter as métricas de consulta, compare as retrievedDocumentCount com as outputDocumentCount da sua consulta. Utilize essa comparação para identificar as secções relevantes que devem ser lidas neste artigo. O retrievedDocumentCount é o número de documentos que o mecanismo de consulta precisa carregar. O outputDocumentCount é o número de documentos que foram necessários para os resultados da consulta. Se o retrievedDocumentCount for significativamente maior que o outputDocumentCount, 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 Obtidos é significativamente superior à 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 obtidos excede a contagem de documentos produzidos

O retrievedDocumentCount é o número de documentos que o mecanismo de consulta precisava carregar. O outputDocumentCount é o número de documentos retornados pela consulta. Se o retrievedDocumentCount for significativamente maior que o outputDocumentCount, 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:

$explain comando:

db.coll.find(
  {
    $and : [
            { "foodGroup" : "Cereal Grains and Pasta"}, 
            { "description" : "Oat bran, cooked"}
        ]
  }
).explain({"executionStatistics": true })

Saída:

{
    "stages" : [ 
        {
            "stage" : "$query",
            "timeInclusiveMS" : 436.5716,
            "timeExclusiveMS" : 436.5716,
            "in" : 1,
            "out" : 1,
            "details" : {
                "database" : "db-test",
                "collection" : "indexing-test",
                "query" : {
                    "$and" : [ 
                        {
                            "foodGroup" : {
                                "$eq" : "Cereal Grains and Pasta"
                            }
                        }, 
                        {
                            "description" : {
                                "$eq" : "Oat bran, cooked"
                            }
                        }
                    ]
                },
                "pathsIndexed" : [],
                "pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ],
                "shardInformation" : [ 
                    {
                        "activityId" : "13a5977e-a10a-4329-b68e-87e4f0081cac",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 435.4867,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1,
                        "retrievedDocumentCount" : 8618
                    }
                ],
                "queryMetrics" : {
                    "retrievedDocumentCount" : 8618,
                    "retrievedDocumentSizeBytes" : 104963042,
                    "outputDocumentCount" : 1,
                    "outputDocumentSizeBytes" : 6064,
                    "indexHitRatio" : 0.0,
                    "totalQueryExecutionTimeMS" : 433.64,
                    "queryPreparationTimes" : {
                        "queryCompilationTimeMS" : 0.12,
                        "logicalPlanBuildTimeMS" : 0.09,
                        "physicalPlanBuildTimeMS" : 0.1,
                        "queryOptimizationTimeMS" : 0.02
                    },
                    "indexLookupTimeMS" : 0,
                    "documentLoadTimeMS" : 387.44,
                    "vmExecutionTimeMS" : 432.93,
                    "runtimeExecutionTimes" : {
                        "queryEngineExecutionTimeMS" : 45.36,
                        "systemFunctionExecutionTimeMS" : 16.86,
                        "userDefinedFunctionExecutionTimeMS" : 0
                    },
                    "documentWriteTimeMS" : 0.13
                }
            }
        }
    ],
    "estimatedDelayFromRateLimitingInMilliseconds" : 0.0,
    "continuation" : {
        "hasMore" : false
    },
    "ok" : 1.0
}

O retrievedDocumentCount (8618) é significativamente maior do que o outputDocumentCount (1), o que implica que esta consulta exigiu uma digitalização de documentos.

Incluir os índices necessários

Você deve verificar a pathsNotIndexed matriz e adicionar esses índices. Neste exemplo, os caminhos foodGroup e description devem ser indexados.

"pathsNotIndexed" : [ 
                    "foodGroup", 
                    "description"
                ]

As práticas recomendadas de indexação na API do Azure Cosmos DB para MongoDB são diferentes do MongoDB. Na API do Azure Cosmos DB para MongoDB, os índices compostos são usados apenas em consultas que precisam classificar eficientemente por várias propriedades. Se você tiver consultas com filtros em várias propriedades, deverá criar índices de campo único para cada uma dessas propriedades. Os predicados de consulta podem usar vários índices de campo único.

Os índices curinga podem simplificar a indexação. Ao contrário do MongoDB, os índices curinga podem suportar vários campos em predicados de consulta. Não haverá diferença no desempenho da consulta se você usar um único índice curinga em vez de criar um índice separado para cada propriedade. Adicionar um índice curinga para todas as propriedades é a maneira mais fácil de otimizar todas as suas consultas.

Você pode adicionar novos índices a qualquer momento, sem efeito na disponibilidade de gravação ou leitura. Você pode acompanhar o progresso da transformação do índice.

Entender quais operações de agregação usam o índice

Na maioria dos casos, as operações de agregação na API do Azure Cosmos DB para MongoDB usarão parcialmente índices. Normalmente, o mecanismo de consulta aplicará filtros de igualdade e intervalo primeiro e usará índices. Depois de aplicar esses filtros, o mecanismo de consulta pode avaliar filtros adicionais e recorrer ao carregamento de documentos restantes para calcular a agregação, se necessário.

Eis um exemplo:

db.coll.aggregate( [
   { $match: { foodGroup: 'Fruits and Fruit Juices' } },
   {
     $group: {
        _id: "$foodGroup",
        total: { $max: "$version" }
     }
   }
] )

Neste caso, os índices podem otimizar o $match palco. Adicionar um índice para foodGroup melhorará significativamente o desempenho da consulta. Como no MongoDB, você deve colocar $match o mais cedo possível no pipeline de agregação para maximizar o uso de índices.

Na API do Azure Cosmos DB para MongoDB, os índices não são usados para a agregação real, que neste caso é $max. Adicionar um índice não melhorará o desempenho da version consulta.

Consultas em que a contagem de documentos recuperados é igual à Contagem de Documentos de Saída

Se o retrievedDocumentCount for aproximadamente igual ao outputDocumentCount, o mecanismo de consulta não precisou digitalizar muitos documentos desnecessários.

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 consulta tiver um filtro de igualdade que corresponda à chave de partição do contentor, terá de verificar apenas o índice da partição relevante. Esta otimização reduz o número total de RUs necessárias para a consulta. Saiba mais sobre as diferenças entre consultas na partição e consultas entre partições.

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.

Você pode verificar a shardInformation matriz para entender as métricas de consulta para cada partição física individual. O número de valores exclusivos shardKeyRangeId é o número de partições físicas onde a consulta precisava ser executada. Neste exemplo, a consulta foi executada em quatro partições físicas. É importante entender que a execução é completamente independente da utilização do índice. Em outras palavras, as consultas entre partições ainda podem usar índices.

  "shardInformation" : [ 
                    {
                        "activityId" : "42f670a8-a201-4c58-8023-363ac18d9e18",
                        "shardKeyRangeId" : "5",
                        "durationMS" : 24.3859,
                        "preemptions" : 1,
                        "outputDocumentCount" : 463,
                        "retrievedDocumentCount" : 463
                    }, 
                    {
                        "activityId" : "a8bf762a-37b9-4c07-8ed4-ae49961373c0",
                        "shardKeyRangeId" : "2",
                        "durationMS" : 35.8328,
                        "preemptions" : 1,
                        "outputDocumentCount" : 905,
                        "retrievedDocumentCount" : 905
                    }, 
                    {
                        "activityId" : "3754e36b-4258-49a6-8d4d-010555628395",
                        "shardKeyRangeId" : "1",
                        "durationMS" : 67.3969,
                        "preemptions" : 1,
                        "outputDocumentCount" : 1479,
                        "retrievedDocumentCount" : 1479
                    }, 
                    {
                        "activityId" : "a69a44ee-db97-4fe9-b489-3791f3d52878",
                        "shardKeyRangeId" : "0",
                        "durationMS" : 185.1523,
                        "preemptions" : 1,
                        "outputDocumentCount" : 867,
                        "retrievedDocumentCount" : 867
                    }
                ]

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 terão 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 tentar executar a consulta quando não houver débito suficiente disponível, o Azure Cosmos DB irá limitar as taxas dos pedidos. Depois de aguardar um curto período de tempo, a API do Azure Cosmos DB para MongoDB repete automaticamente esta consulta. Os pedidos com limitação demoram mais tempo, pelo que aumentar o débito aprovisionado pode melhorar a latência das consultas.

O valor estimatedDelayFromRateLimitingInMilliseconds dá uma noção dos potenciais benefícios de latência se você aumentar a taxa de transferência.

Próximos passos