Compartir vía


Obtención de métricas de ejecución de consultas SQL y análisis del rendimiento de las consultas mediante el SDK de .NET

SE APLICA A: NoSQL

En este artículo se muestra cómo generar perfiles de rendimiento de consulta SQL en Azure Cosmos DB mediante ServerSideCumulativeMetrics recuperado del SDK de .NET. ServerSideCumulativeMetrics es un objeto fuertemente tipado con información sobre la ejecución de consultas de back-end. Contiene métricas acumulativas que se agregan en todas las particiones físicas de la solicitud, una lista de métricas para cada partición física y el cargo total de la solicitud. Estas métricas se documentan con más detalle en el artículo Ajuste el rendimiento de las consultas.

Obtención de las métricas de consulta

Las métricas de consulta están disponibles como un objeto fuertemente tipado en el SDK de .NET a partir de la versión 3.36.0. Antes de esta versión, o si usa otro lenguaje del SDK, puede recuperar las métricas de consulta analizando el Diagnostics. En el ejemplo de código siguiente se muestra cómo recuperar ServerSideCumulativeMetrics del Diagnostics en un 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();
}

También puede obtener métricas de consulta del FeedResponse de una consulta LINQ mediante el 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 acumulativas

ServerSideCumulativeMetrics contiene una propiedad CumulativeMetrics que representa las métricas de consulta agregadas en todas las particiones para el recorrido de ida y vuelta único.

// 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;

También puede agregar estas métricas en todos los recorridos de ida y vuelta de la consulta. A continuación se muestra un ejemplo de cómo agregar el tiempo de ejecución de consulta en todos los recorridos de ida y vuelta de una consulta determinada mediante 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 con particiones

ServerSideCumulativeMetrics contiene una propiedad PartitionedMetrics que es una lista de métricas por partición para el recorrido de ida y vuelta. Si se alcanzan varias particiones físicas en un solo recorrido de ida y vuelta, las métricas para cada una de ellas aparecen en la lista. Las métricas con particiones se representan como ServerSidePartitionedMetrics con un identificador único para cada partición física y un cargo de solicitud para esa partición.

// 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;

Cuando se acumula en todos los recorridos de ida y vuelta, las métricas por partición permiten ver si una partición específica está causando problemas de rendimiento en comparación con otras. A continuación se muestra un ejemplo de cómo agrupar las métricas de partición para cada viaje mediante 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();
    }
}

Obtener el cargo de la solicitud de consulta

Puede capturar las unidades de solicitud consumidas por cada consulta para investigar consultas costosas o consultas que consumen un alto rendimiento. Puede obtener el cargo total de solicitud mediante la propiedad TotalRequestCharge en ServerSideCumulativeMetrics o puede examinar el cargo de solicitud de cada partición mediante la propiedad RequestCharge para cada ServerSidePartitionedMetrics devuelto.

El cargo total de solicitud también está disponible mediante la propiedad RequestCharge en FeedResponse. Para obtener más información acerca de cómo obtener el cargo de la solicitud mediante Azure Portal y los diferentes SDK, consulte el artículo sobre la búsqueda del cargo de la unidad de solicitud.

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);
}

Obtener el tiempo de ejecución de consulta

Puede capturar el tiempo de ejecución de las consulta para cada viaje desde las métricas de consulta. Al examinar la latencia de la solicitud, es importante diferenciar el tiempo de ejecución de consulta de otros orígenes de latencia, como el tiempo de tránsito de red. En el ejemplo siguiente se muestra cómo obtener el tiempo de ejecución de consulta acumulativo para cada recorrido de ida y vuelta:

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);

Obtener el uso del índice

Examinar el uso del índice puede ayudarle a depurar consultas lentas. Las consultas que no pueden usar el índice dan como resultado un examen completo de todos los documentos de un contenedor antes de devolver el conjunto de resultados.

Este es un ejemplo de una consulta de examen:

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

El filtro de esta consulta usa la función del sistema UPPER, que no se sirve desde el índice. La ejecución de esta consulta en una colección grande genera las siguientes métricas de consulta para la primera continuación:

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 los siguientes valores de la salida de las métricas de consulta:

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

Esta consulta cargó 60 951 documentos, que sumaban un total de 399 998 938 bytes. La carga de este número de bytes da lugar a un alto costo o cargo de unidad de solicitud. También se tarda mucho tiempo en ejecutar la consulta, lo que se ve claramente con la propiedad de tiempo total transcurrido:

Total Query Execution Time               :        4,500.34 milliseconds

Esto significa que la consulta tardó 4,5 segundos en ejecutarse (y solo era una continuación).

Para optimizar esta consulta de ejemplo, evite el uso de UPPER en el filtro. En su lugar, cuando se crean o actualizan los documentos, los valores c.description se deben insertar completamente en mayúsculas. La consulta se convierte en lo siguiente:

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

Ahora esta consulta puede servirse desde el índice. Como alternativa, puede usar propiedades calculadas para indexar los resultados de las funciones del sistema o cálculos complejos que, de lo contrario, generarían un examen completo.

Para más información acerca de cómo ajustar el rendimiento de las consultas, consulte el artículo Ajuste del rendimiento de las consultas.

Referencias

Pasos siguientes