Compartilhar via


Obter métricas de execução da consulta SQL e analisar o desempenho de consulta usando o SDK do .NET

APLICA-SE A: NoSQL

Este artigo apresenta como analisar o desempenho de consultas SQL no Azure Cosmos DB usando o ServerSideCumulativeMetrics recuperado do SDK do .NET. O ServerSideCumulativeMetrics é um objeto fortemente tipado com informações sobre a execução da consulta de back-end. Ele contém métricas cumulativas agregadas em todas as partições físicas para a solicitação e uma lista de métricas para cada partição física e o preço total da solicitação. Essas métricas são documentadas com mais detalhes no artigo Ajustar o Desempenho da Consulta.

Obter métricas de consulta

As métricas de consulta estão disponíveis como um objeto fortemente tipado no SDK do .NET a partir da versão 3.36.0. Antes dessa versão ou se você estiver usando uma linguagem do SDK diferente, você poderá recuperar métricas de consulta analisando o Diagnostics. O exemplo de código a seguir mostra como recuperar ServerSideCumulativeMetrics do Diagnostics em um FeedResponse:

CosmosClient client = new CosmosClient(myCosmosEndpoint, myCosmosKey);
Container container = client.GetDatabase(myDatabaseName).GetContainer(myContainerName);

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
    ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}

Você também pode obter métricas de consulta do FeedResponse de uma consulta LINQ usando o método ToFeedIterator():

FeedIterator<MyClass> feedIterator = container.GetItemLinqQueryable<MyClass>()
    .Take(5)
    .ToFeedIterator();

while (feedIterator.HasMoreResults)
{
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();
}

Métricas cumulativas

ServerSideCumulativeMetrics contém uma propriedade CumulativeMetrics que representa as métricas de consulta agregadas em todas as partições para a única viagem de ida e volta.

// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();

// CumulativeMetrics is the metrics for this continuation aggregated over all partitions
ServerSideMetrics cumulativeMetrics = metrics.CumulativeMetrics;

Você também pode agregar essas métricas em todas as viagens de ida e volta para a consulta. Veja o seguinte exemplo de como agregar o tempo de execução da consulta em todas as viagens de ida e volta para uma determinada consulta usando LINQ:

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
TimeSpan cumulativeTime;
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
    metrics.Add(feedResponse.Diagnostics.GetQueryMetrics());
}

// Aggregate values across trips for metrics of interest
TimeSpan totalTripsExecutionTime = metrics.Aggregate(TimeSpan.Zero, (currentSum, next) => currentSum + next.CumulativeMetrics.TotalTime);
DoSomeLogging(totalTripsExecutionTime);

Métricas particionadas

ServerSideCumulativeMetrics contém uma propriedade PartitionedMetrics que é uma lista de métricas por partição para a viagem de ida e volta. Se várias partições físicas forem alcançadas em apenas uma viagem de ida e volta, as métricas para cada uma delas aparecerão na lista. As métricas particionadas são representadas como ServerSidePartitionedMetrics com um identificador exclusivo para cada partição física e o preço da solicitação para essa partição.

// Retrieve the ServerSideCumulativeMetrics object from the FeedResponse
ServerSideCumulativeMetrics metrics = feedResponse.Diagnostics.GetQueryMetrics();

// PartitionedMetrics is a list of per-partition metrics for this continuation
List<ServerSidePartitionedMetrics> partitionedMetrics = metrics.PartitionedMetrics;

Quando acumuladas em todas as viagens de ida e volta, as métricas por partição permitem que você veja se uma partição específica está causando problemas de desempenho quando comparada com outras. Veja o seguinte exemplo de como agrupar métricas de partição para cada viagem usando o LINQ:

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

List<ServerSideCumulativeMetrics> metrics = new List<ServerSideCumulativeMetrics>();
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();

    // Store the ServerSideCumulativeMetrics object to aggregate values after all round trips
    metrics.Add(feedResponse.Diagnostics.GetQueryMetrics());
}

// Group metrics by partition key range id
var groupedPartitionMetrics = metrics.SelectMany(m => m.PartitionedMetrics).GroupBy(p => p.PartitionKeyRangeId);
foreach(var partitionGroup in groupedPartitionMetrics)
{
    foreach(var tripMetrics in partitionGroup)
    {
        DoSomethingWithMetrics();
    }
}

Obter o preço da solicitação de consulta

Você pode capturar as unidades de solicitação consumidas por todas as consultas para investigar consultas dispendiosas ou consultas que consomem alta taxa de transferência. Você pode obter o cobrança total da solicitação usando a propriedade TotalRequestCharge em ServerSideCumulativeMetrics ou examinar o preço da solicitação de cada partição usando a propriedade RequestCharge para cada ServerSidePartitionedMetrics retornado.

O preço total da solicitação também está disponível usando a propriedade RequestCharge em FeedResponse. Para saber mais sobre como obter o preço da solicitação usando portal do Azure e diferentes SDKs, confira o artigo encontrar a unidade de solicitação.

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    double requestCharge = feedResponse.RequestCharge;

    // Log the RequestCharge how ever you want.
    DoSomeLogging(requestCharge);
}

Obter o tempo de execução da consulta

Você pode capturar o tempo de execução da consulta para cada viagem das métricas de consulta. Ao examinar a latência da solicitação, é importante diferenciar o tempo de execução da consulta de outras fontes de latência, como o tempo de trânsito de rede. O seguinte exemplo mostra como obter o tempo de execução de consulta cumulativo para cada viagem de ida e volta:

QueryDefinition query = new QueryDefinition("SELECT TOP 5 * FROM c");
FeedIterator<MyClass> feedIterator = container.GetItemQueryIterator<MyClass>(query);

TimeSpan cumulativeTime;
while (feedIterator.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<MyClass> feedResponse = await feedIterator.ReadNextAsync();
    ServerSideCumulativeMetrics metrics = response.Diagnostics.GetQueryMetrics();
    cumulativeTime = metrics.CumulativeMetrics.TotalTime;
}

// Log the elapsed time
DoSomeLogging(cumulativeTime);

Obter a utilização do Índice

Examinar a utilização do índice pode ajudar você a depurar consultas lentas. As consultas que não podem usar o índice resultam em uma verificação completa de todos os documentos em um contêiner antes de retornar o conjunto de resultados.

Aqui está um exemplo de uma consulta de verificação:

SELECT VALUE c.description 
FROM   c 
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

O filtro dessa consulta usa a função do sistema UPPER, que não é servida pelo índice. Executar essa consulta em uma coleção grande produziu as seguintes métricas de consulta para a primeira continuação:

QueryMetrics

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 Time                   :             0.2 milliseconds
Index Lookup Time                        :            0.01 milliseconds
Document Load Time                       :        4,177.66 milliseconds
Runtime Execution Time                   :           407.9 milliseconds
Document Write Time                      :            0.01 milliseconds

Observe os seguintes valores da saída das métricas de consulta:

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes

Essa consulta carregou 60.951 documentos, que totalizaram 399.998.938 bytes. O carregamento desse número de bytes gera um alto custo ou preço por unidade de solicitação. Também leva muito tempo para executar a consulta, o que fica claro com a propriedade de tempo total gasto:

Total Query Execution Time               :        4,500.34 milliseconds

Isso significa que a consulta levou 4,5 segundos para ser executada (e essa era apenas uma continuação).

Para otimizar essa consulta de exemplo, evite o uso de UPPER no filtro. Em vez disso, quando documentos forem criados ou atualizados, os valores c.description deverão ser inseridos em todos os caracteres maiúsculos. A consulta então se torna:

SELECT VALUE c.description 
FROM   c 
WHERE c.description = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

Agora, esta consulta pode ser servida pelo índice. Como alternativa, você pode usar propriedades computadas para indexar os resultados de funções do sistema ou cálculos complexos que, de outra forma, resultariam em uma verificação completa.

Para saber mais sobre como ajustar o desempenho da consulta, confira o artigo Ajustar o desempenho da consulta.

Referências

Próximas etapas