Compartilhar via


Solucionar problemas de consulta ao usar o Azure Cosmos DB for MongoDB

APLICA-SE AO: MongoDB

Este artigo percorre uma abordagem geral recomendada para a soluçã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. Use 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, confira o artigo do Guia de solução de problemas de consulta da API do NoSQL.

As otimizações de consulta no Azure Cosmos DB são amplamente categorizadas da seguinte maneira:

  • Otimizações que reduzem o preço de RU (unidade de solicitação) da consulta
  • Otimizações que apenas reduzem a latência

As otimizações de consulta no Azure Cosmos DB são amplamente categorizadas da seguinte maneira:

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

Observação

Este artigo pressupõe que você esteja usando a API do Azure Cosmos DB para contas do MongoDB com a versão 3.6 e superior. Algumas consultas com desempenho insatisfatório na versão 3.2 tiveram melhorias significativas nas versões 3.6 e superiores. Atualize para a versão 3.6 enviando uma solicitação de suporte.

Execute o comando $explain para obter métricas:

Quando você otimiza uma consulta no Azure Cosmos DB, a primeira etapa é sempre obter as métricas de preço de RU para a consulta. Uma diretriz aproximada é explorar maneiras de reduzir a carga de RU para consultas com preços acima de 50 RUs.

Além de obter o preço de RU, use o comando $explain para obter as métricas de uso de consulta e índice. Segue um exemplo que executa uma consulta e usa o comando $explain para mostrar as métricas de uso de consulta e índice:

Comando $explain:

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 saída do comando $explain é extensa e tem informações detalhadas sobre a execução da consulta. Porém, em geral, há algumas seções nas quais você deve se concentrar ao otimizar o desempenho da consulta:

Métrica Descrição
timeInclusiveMS Latência de consulta de back-end
pathsIndexed Mostra os índices usados pela consulta
pathsNotIndexed Mostra os índices que a consulta poderia ter usado, se disponíveis
shardInformation Resumo do desempenho da consulta de 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 da consulta adicional estimada devido à limitação de taxa

Depois de obter as métricas de consulta, compare a retrievedDocumentCount com a outputDocumentCount para sua consulta. Use essa comparação para identificar as seções relevantes que serão examinadas neste artigo. A retrievedDocumentCount é o número de documentos que o mecanismo de consulta precisa carregar. A outputDocumentCount é o número de documentos que foram necessários para os resultados da consulta. Se a retrievedDocumentCount for significativamente maior do que a outputDocumentCount, haverá pelo menos uma parte da sua consulta que não conseguiu usar um índice e que precisava fazer uma verificação.

Veja as seções a seguir para entender as otimizações de consulta relevantes para seu cenário.

O preço de RU da consulta é muito alto

A Contagem de Documentos Recuperados é significativamente maior que a Contagem de Documentos de Saída

A Contagem de Documentos Recuperados é aproximadamente igual à Contagem de Documentos de Saída

O preço de RU da consulta é aceitável, mas a latência ainda é muito alta

Consultas em que a contagem de documentos recuperados supera a contagem de documentos de saída

A retrievedDocumentCount é o número de documentos que o mecanismo de consulta precisava carregar. A outputDocumentCount é o número de documentos retornados pela consulta. Se a retrievedDocumentCount for significativamente maior do que a outputDocumentCount, haverá pelo menos uma parte da sua consulta que não conseguiu usar um índice e que precisava fazer uma verificação.

Veja um exemplo de consulta de verificação que não foi totalmente servida pelo índice:

Comando $explain:

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 que o outputDocumentCount (1), o que significa que essa consulta precisava de uma verificação de documento.

Incluir os índices necessários

Verifique a matriz pathsNotIndexed e adicione esses índices. Neste exemplo, os caminhos foodGroup e description precisam ser indexados.

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

As práticas recomendadas para 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 somente em consultas que precisam classificar com eficiência por várias propriedades. Se você tiver consultas com filtros em múltiplas propriedades, crie índices de campo único para cada uma dessas propriedades. Os predicados de consulta podem usar múltiplos índices de campo único.

Índices curingas podem simplificar a indexação. Diferente do MongoDB, os índices curinga podem dar suporte a vários campos em predicados de consulta. Não fará 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 forma mais fácil de otimizar todas as suas consultas.

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

Saiba 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 os índices parcialmente. Normalmente, o mecanismo de consulta primeiro aplicará filtros de igualdade e intervalo e usará índices. Depois de aplicar esses filtros, o mecanismo de consulta poderá avaliar filtros adicionais e recorrer ao carregamento de documentos restantes para computar a agregação, se necessário.

Veja um exemplo:

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

Nesse caso, os índices podem otimizar o estágio $match. Adicionar um índice para foodGroup melhorará significativamente o desempenho da consulta. Como no MongoDB, você deve colocar $match o quanto antes no pipeline de agregação para maximizar o uso de índices.

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

Consultas cuja 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 precisará verificar muitos documentos desnecessários.

Minimizar consultas entre partições

O Azure Cosmos DB usa particionamento para escalar contêineres individuais à medida que as necessidades de armazenamento de dados e da Unidade de Solicitação 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 contêiner, você precisará verificar apenas o índice da partição relevante. Essa otimização reduz o número total de RUs que a consulta requer. Saiba mais sobre as diferenças entre as consultas em partição e consultas entre partições.

Se você tiver um grande número de RUs provisionados (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 nos preços de RU de consulta.

Verifique a matriz shardInformation 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 em que a consulta precisava ser executada. No exemplo, a consulta foi executada em quatro partições físicas. É importante saber que a execução é totalmente independente da utilização do índice. Em outras palavras, 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 preço de RU pode ser aceitável quando a latência de consulta ainda é muito alta. As seções a seguir fornecem uma visão geral das dicas para reduzir a latência da consulta. Se você executar a mesma consulta várias vezes no mesmo conjunto de dados, ela normalmente terá o mesmo preço de RU. Mas a latência de consulta pode variar entre as execuções de consulta.

Aprimora a proximidade

As consultas executadas de uma região diferente da conta do Azure Cosmos DB terão uma latência maior do que se fossem executadas dentro da mesma região. Por exemplo, se você estiver executando código em seu computador desktop, deverá esperar que a latência seja de dezenas ou centenas de milissegundos maior (ou mais) do que se a consulta vier de uma máquina virtual na mesma região do Azure que o Azure Cosmos DB. É simples distribuir dados globalmente no Azure Cosmos DB para garantir que você possa trazer seus dados mais perto do seu aplicativo.

Aumentar a taxa de transferência provisionada

No Azure Cosmos DB, sua taxa de transferência provisionada é medida em RUs (unidades de solicitação). Imagine que você tenha uma consulta que consome 5 RUs de taxa de transferência. Por exemplo, se você provisionar 1.000 RUs, poderá executar essa consulta 200 vezes por segundo. Se você tentar executar a consulta quando não houver taxa de transferência suficiente disponível, o Azure Cosmos DB limitará a taxa de solicitações. A API do Azure Cosmos DB para o MongoDB repetirá essa consulta automaticamente depois de aguardar por um curto período. As solicitações restritas demoram mais, portanto, aumentar a taxa de transferência provisionada pode aprimorar a latência da consulta.

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

Próximas etapas