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

APLICA-SE A: NoSQL

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

Obter métricas da 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 desta versão, ou se você estiver usando um idioma SDK diferente, poderá recuperar métricas de consulta analisando o Diagnosticsarquivo . 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 de FeedResponse uma consulta LINQ usando o ToFeedIterator() método:

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 CumulativeMetrics propriedade 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. A seguir está um exemplo de como agregar o tempo de execução da consulta em todas as viagens de ida e volta para uma determinada consulta 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>();
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(response.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 PartitionedMetrics propriedade 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 uma única 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 taxa de 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 a outras. A seguir está um 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(response.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 a taxa de solicitação de consulta

Você pode capturar as unidades de solicitação consumidas por cada consulta para investigar consultas caras ou consultas que consomem alta taxa de transferência. Você pode obter a taxa de solicitação total usando a TotalRequestCharge propriedade em ServerSideCumulativeMetrics ou você pode olhar para a taxa de solicitação de cada partição usando a RequestCharge propriedade para cada ServerSidePartitionedMetrics devolvido.

A taxa de solicitação total também está disponível usando a RequestCharge propriedade em FeedResponse. Para saber mais sobre como obter a cobrança de solicitação usando o portal do Azure e SDKs diferentes, consulte o artigo Localizar a taxa de 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 a partir das métricas de consulta. Ao analisar 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 da rede. O exemplo a seguir mostra como obter o tempo de execução de consulta cumulativa 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

Observar a utilização do índice pode ajudá-lo 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.

Eis um exemplo de uma consulta de análise:

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

O filtro desta consulta usa a função do sistema UPPER, que não é servida a partir do índice. A execução dessa 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

Esta consulta carregou 60.951 documentos, que totalizaram 399.998.938 bytes. Carregar esses muitos bytes resulta em alto custo ou taxa unitária de solicitação. Também leva muito tempo para executar a consulta, o que fica claro com o tempo total gasto na propriedade:

Total Query Execution Time               :        4,500.34 milliseconds

O que significa que a consulta demorou 4,5 segundos a ser executada (e esta foi apenas uma continuação).

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

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

Esta consulta pode agora ser servida a partir do í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, consulte o artigo Ajustar desempenho da consulta.

Referências

Próximos passos