Mendapatkan metrik eksekusi kueri SQL dan menganalisis kinerja kueri menggunakan .NET SDK

BERLAKU UNTUK: SQL API

Artikel ini menyajikan cara membuat profil kinerja kueri SQL di Azure Cosmos DB. Pembuatan profil ini dapat dilakukan menggunakan QueryMetrics yang diambil dari SDK .NET dan dijelaskan secara mendetail di sini. QueryMetrics adalah objek dengan batasan penulisan ketat, berisi informasi tentang eksekusi kueri ujung belakang. Metrik ini didokumentasikan secara lebih mendetail dalam artikel Menyelaraskan Kinerja Kueri.

Mengatur parameter FeedOptions

Semua kelebihan beban untuk DocumentClient.CreateDocumentQuery mengambil parameter FeedOptions opsional. Opsi inilah yang memungkinkan eksekusi kueri diselaraskan dan dibuat parameternya.

Untuk mengumpulkan metrik eksekusi kueri Sql, Anda harus mengatur parameter PopulateQueryMetrics dalam FeedOptions ke true. Mengatur PopulateQueryMetrics ke true akan membuat FeedResponse berisi QueryMetrics yang relevan.

Mendapatkan metrik kueri dengan AsDocumentQuery()

Contoh kode berikut menunjukkan cara mengambil metrik saat menggunakan metode AsDocumentQuery():

// Initialize this DocumentClient and Collection
DocumentClient documentClient = null;
DocumentCollection collection = null;

// Setting PopulateQueryMetrics to true in the FeedOptions
FeedOptions feedOptions = new FeedOptions
{
    PopulateQueryMetrics = true
};

string query = "SELECT TOP 5 * FROM c";
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();

while (documentQuery.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
    
    // This dictionary maps the partitionId to the QueryMetrics of that query
    IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
    
    // At this point you have QueryMetrics which you can serialize using .ToString()
    foreach (KeyValuePair<string, QueryMetrics> kvp in partitionIdToQueryMetrics)
    {
        string partitionId = kvp.Key;
        QueryMetrics queryMetrics = kvp.Value;
        
        // Do whatever logging you need
        DoSomeLoggingOfQueryMetrics(query, partitionId, queryMetrics);
    }
}

Menggabungkan QueryMetrics

Di bagian sebelumnya, perhatikan bahwa ada beberapa panggilan ke metode ExecuteNextAsync. Setiap panggilan mengembalikan objek FeedResponse yang memiliki kamus QueryMetrics; satu untuk setiap kelanjutan kueri. Contoh berikut menunjukkan cara menggabungkan QueryMetrics ini menggunakan LINQ:

List<QueryMetrics> queryMetricsList = new List<QueryMetrics>();

while (documentQuery.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
    
    // This dictionary maps the partitionId to the QueryMetrics of that query
    IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
    queryMetricsList.AddRange(partitionIdToQueryMetrics.Values);
}

// Aggregate the QueryMetrics using the + operator overload of the QueryMetrics class.
QueryMetrics aggregatedQueryMetrics = queryMetricsList.Aggregate((curr, acc) => curr + acc);
Console.WriteLine(aggregatedQueryMetrics);

Mengelompokkan metrik kueri menurut ID Partisi

Anda dapat mengelompokkan QueryMetrics menurut ID Partisi. Pengelompokan menurut ID Partisi memungkinkan Anda melihat apakah Partisi tertentu menyebabkan masalah kinerja dibanding yang lain. Contoh berikut menunjukkan cara mengelompokkan QueryMetrics dengan LINQ:

List<KeyValuePair<string, QueryMetrics>> partitionedQueryMetrics = new List<KeyValuePair<string, QueryMetrics>>();
while (documentQuery.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
    
    // This dictionary is maps the partitionId to the QueryMetrics of that query
    IReadOnlyDictionary<string, QueryMetrics> partitionIdToQueryMetrics = feedResponse.QueryMetrics;
    partitionedQueryMetrics.AddRange(partitionIdToQueryMetrics.ToList());
}

// Now we are able to group the query metrics by partitionId
IEnumerable<IGrouping<string, KeyValuePair<string, QueryMetrics>>> groupedByQueryMetrics = partitionedQueryMetrics
    .GroupBy(kvp => kvp.Key);

// If we wanted to we could even aggregate the groupedby QueryMetrics
foreach(IGrouping<string, KeyValuePair<string, QueryMetrics>> grouping in groupedByQueryMetrics)
{
    string partitionId = grouping.Key;
    QueryMetrics aggregatedQueryMetricsForPartition = grouping
        .Select(kvp => kvp.Value)
        .Aggregate((curr, acc) => curr + acc);
    DoSomeLoggingOfQueryMetrics(query, partitionId, aggregatedQueryMetricsForPartition);
}

LINQ di DocumentQuery

Anda juga dapat memperoleh FeedResponse dari Kueri LINQ menggunakan metode AsDocumentQuery():

IDocumentQuery<Document> linqQuery = client.CreateDocumentQuery(collection.SelfLink, feedOptions)
    .Take(1)
    .Where(document => document.Id == "42")
    .OrderBy(document => document.Timestamp)
    .AsDocumentQuery();
FeedResponse<Document> feedResponse = await linqQuery.ExecuteNextAsync<Document>();
IReadOnlyDictionary<string, QueryMetrics> queryMetrics = feedResponse.QueryMetrics;

Kueri Mahal

Anda dapat mencatat unit permintaan yang digunakan oleh setiap kueri untuk menyelidiki kueri mahal atau kueri yang menggunakan throughput tinggi. Anda dapat memperoleh biaya permintaan dengan properti RequestCharge di FeedResponse. Untuk mempelajari selengkapnya tentang cara mendapatkan biaya permintaan menggunakan portal Azure dan SDK lain, lihat artikel menemukan biaya unit permintaan.

string query = "SELECT * FROM c";
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();

while (documentQuery.HasMoreResults)
{
    // Execute one continuation of the query
    FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
    double requestCharge = feedResponse.RequestCharge
    
    // Log the RequestCharge how ever you want.
    DoSomeLogging(requestCharge);
}

Mendapatkan waktu eksekusi kueri

Saat menghitung waktu yang diperlukan untuk menjalankan kueri pihak klien, pastikan Anda hanya menyertakan waktu untuk memanggil metode ExecuteNextAsync dan bukan bagian lain basis kode Anda. Hanya panggilan ini yang membantu Anda menghitung waktu eksekusi kueri berjalan seperti yang ditunjukkan dalam contoh berikut:

string query = "SELECT * FROM c";
IDocumentQuery<dynamic> documentQuery = documentClient.CreateDocumentQuery(Collection.SelfLink, query, feedOptions).AsDocumentQuery();
Stopwatch queryExecutionTimeEndToEndTotal = new Stopwatch();
while (documentQuery.HasMoreResults)
{
    // Execute one continuation of the query
    queryExecutionTimeEndToEndTotal.Start();
    FeedResponse<dynamic> feedResponse = await documentQuery.ExecuteNextAsync();
    queryExecutionTimeEndToEndTotal.Stop();
}

// Log the elapsed time
DoSomeLogging(queryExecutionTimeEndToEndTotal.Elapsed);

Kueri pemindaian (umumnya lambat dan mahal)

Kueri pemindaian merujuk ke kueri yang tidak dilayani oleh indeks, karena itu, banyak dokumen dimuat sebelum mengembalikan set hasil.

Di bawah ini adalah contoh kueri pemindaian:

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

Filter kueri ini menggunakan fungsi sistem UPPER, yang tidak disajikan dari indeks. Eksekusi kueri ini terhadap kumpulan besar menghasilkan metrik kueri berikut untuk kelanjutan pertama:

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 Times
    Query Compilation Time               :            0.09 milliseconds
    Logical Plan Build Time              :            0.05 milliseconds
    Physical Plan Build Time             :            0.04 milliseconds
    Query Optimization Time              :            0.01 milliseconds
  Index Lookup Time                      :            0.01 milliseconds
  Document Load Time                     :        4,177.66 milliseconds
  Runtime Execution Times
    Query Engine Times                   :          322.16 milliseconds
    System Function Execution Time       :           85.74 milliseconds
    User-defined Function Execution Time :            0.00 milliseconds
  Document Write Time                    :            0.01 milliseconds
Client Side Metrics
  Retry Count                            :               0
  Request Charge                         :        4,059.95 RUs

Perhatikan nilai berikut dari output metrik kueri:

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

Kueri ini memuat 60.951 dokumen, yang berjumlah 399.998.938 byte. Pemuatan byte sebanyak ini menghasilkan biaya tinggi atau biaya unit permintaan. Diperlukan juga waktu yang lama untuk menjalankan kueri, yang jelas dengan total waktu yang dihabiskan properti:

Total Query Execution Time               :        4,500.34 milliseconds

Artinya kueri membutuhkan waktu 4,5 detik untuk berjalan (dan ini hanya satu kelanjutan).

Untuk mengoptimalkan kueri contoh ini, hindari penggunaan UPPER dalam filter. Sebagai gantinya, ketika dokumen dibuat atau diperbarui, nilai c.description harus disisipkan dalam karakter huruf besar seluruhnya. Kueri kemudian menjadi:

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

Kueri ini kini dapat dilayani dari indeks.

Untuk mempelajari selengkapnya tentang penyelarasan kinerja kueri, lihat artikel Menyelaraskan Kinerja Kueri.

Referensi

Langkah berikutnya