Memecahkan masalah kueri saat menggunakan Azure Cosmos DB for MongoDB

BERLAKU UNTUK: MongoDB

Artikel ini berjalan melalui pendekatan umum yang direkomendasikan untuk memecahkan masalah kueri di Azure Cosmos DB. Meskipun Anda tidak boleh mempertimbangkan langkah-langkah yang diuraikan dalam artikel ini pertahanan lengkap terhadap masalah kueri potensial, kami telah menyertakan tips performa yang paling umum di sini. Anda harus menggunakan artikel ini sebagai langkah awal untuk memecahkan masalah kueri lambat atau mahal di API Azure Cosmos DB untuk MongoDB. Jika Anda menggunakan Azure Cosmos DB untuk NoSQL, lihat artikel panduan pemecahan masalah kueri API untuk NoSQL.

Pengoptimalan kueri di Azure Cosmos DB secara luas dikategorikan sebagai berikut:

  • Pengoptimalan yang mengurangi biaya Unit Permintaan (RU) kueri
  • Pengoptimalan yang hanya mengurangi latensi

Jika Anda mengurangi muatan RU kueri, biasanya Anda juga akan mengurangi latensi.

Artikel ini menyediakan contoh yang dapat Anda buat ulang dengan menggunakan himpunan data nutrisi.

Catatan

Artikel ini mengasumsikan Anda menggunakan API Azure Cosmos DB untuk akun MongoDB dengan versi 3.6 dan yang lebih tinggi. Beberapa kueri yang berkinerja buruk di versi 3.2 memiliki peningkatan signifikan dalam versi 3.6+. Tingkatkan ke versi 3.6 dengan mengajukan permintaan dukungan.

Gunakan perintah $explain untuk mendapatkan metrik

Saat Anda mengoptimalkan kueri di Azure Cosmos DB, langkah pertama adalah selalu mendapatkan biaya RU untuk kueri Anda. Sebagai pedoman kasar, Anda harus mengeksplorasi cara-cara untuk menurunkan biaya RU untuk pertanyaan dengan biaya lebih dari 50 RUs.

Selain mendapatkan biaya RU, Anda harus menggunakan perintah $explain untuk mendapatkan metrik penggunaan kueri dan indeks. Berikut adalah contoh yang menjalankan kueri dan menggunakan perintah $explain untuk memperlihatkan metrik penggunaan kueri dan indeks:

Perintah $explain:

db.coll.find({foodGroup: "Baby Foods"}).explain({"executionStatistics": true })

Output:

{
    "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
}

Perintah $explain output bersifat panjang dan memiliki informasi terperinci tentang eksekusi kueri. Namun, secara umum, ada beberapa bagian di mana Anda harus fokus saat mengoptimalkan performa kueri:

Metrik Deskripsi
timeInclusiveMS Latensi kueri backend
pathsIndexed Memperlihatkan indeks yang digunakan kueri
pathsNotIndexed Memperlihatkan indeks yang bisa digunakan kueri, jika tersedia
shardInformation Ringkasan performa kueri untuk partisi fisik tertentu
retrievedDocumentCount Jumlah dokumen yang dimuat oleh mesin kueri
outputDocumentCount Jumlah dokumen yang dikembalikan dalam hasil kueri
estimatedDelayFromRateLimitingInMilliseconds Estimasi latensi kueri tambahan karena pembatasan tarif

Setelah Anda mendapatkan metrik kueri, bandingkan retrievedDocumentCount dengan metrik outputDocumentCount untuk kueri Anda. Gunakan perbandingan ini untuk mengidentifikasi bagian yang relevan untuk ditinjau dalam artikel ini. retrievedDocumentCount adalah jumlah dokumen yang perlu dimuat oleh mesin kueri. outputDocumentCount adalah jumlah dokumen yang diperlukan untuk hasil kueri. Jika retrievedDocumentCount secara signifikan lebih tinggi dari outputDocumentCount, setidaknya ada satu bagian dari kueri Anda yang tidak dapat menggunakan indeks dan perlu melakukan pemindaian.

Lihat bagian berikut untuk memahami pengoptimalan kueri yang relevan untuk skenario Anda.

Biaya RU Kueri terlalu tinggi

Jumlah Dokumen yang Diambil secara signifikan lebih tinggi dari Jumlah Dokumen Output

Jumlah Dokumen yang Diambil kira-kira sama dengan Jumlah Dokumen Output

Biaya RU Kueri dapat diterima tetapi latensi masih terlalu tinggi

Kueri di mana jumlah dokumen yang diperoleh melebihi jumlah dokumen k

retrievedDocumentCount adalah jumlah dokumen yang perlu dimuat oleh mesin kueri. outputDocumentCount adalah jumlah dokumen yang dikembalikan oleh kueri. Jika retrievedDocumentCount secara signifikan lebih tinggi dari outputDocumentCount, setidaknya ada satu bagian dari kueri Anda yang tidak dapat menggunakan indeks dan perlu melakukan pemindaian.

Berikut ini contoh kueri pemindaian yang tidak sepenuhnya dilayani oleh indeks:

Perintah $explain:

db.coll.find(
  {
    $and : [
            { "foodGroup" : "Cereal Grains and Pasta"}, 
            { "description" : "Oat bran, cooked"}
        ]
  }
).explain({"executionStatistics": true })

Output:

{
    "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
}

retrievedDocumentCount(8618) secara signifikan lebih tinggi darioutputDocumentCount (1), menyiratkan bahwa kueri ini memerlukan pemindaian dokumen.

Menyertakan indeks yang diperlukan

Anda harus memeriksa pathsNotIndexed array dan menambahkan indeks ini. Sebagai contoh, jalur foodGroup dan description harus diindeks.

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

Mengindeks praktik terbaik di API Azure Cosmos DB untuk MongoDB berbeda dengan MongoDB. Dalam AZURE Cosmos DB's API for MongoDB, indeks majemuk hanya digunakan dalam kueri yang perlu diurutkan secara efisien oleh beberapa properti. Jika Anda memiliki kueri dengan filter pada beberapa properti, Anda harus membuat indeks bidang tunggal untuk setiap properti ini. Predikat kueri bisa menggunakan beberapa indeks bidang tunggal.

Indeks wildcard dapat menyederhanakan pengindeksan. Tidak seperti di MongoDB, indeks kartubebas dapat mendukung beberapa bidang dalam predikat kueri. Di dalam performa kueri tidak akan ada perbedaan jika Anda menggunakan satu indeks kartubebas tunggal alih-alih membuat indeks terpisah untuk setiap properti. Menambahkan indeks wildcard untuk semua properti adalah cara termudah untuk mengoptimalkan semua kueri Anda.

Anda dapat menambahkan indeks baru kapan saja, tanpa berpengaruh pada ketersediaan tulis atau baca. Anda dapat melacak kemajuan transformasi indeks.

Memahami operasi agregasi mana yang menggunakan indeks

Dalam kebanyakan kasus, operasi agregasi di Azure Cosmos DB's API for MongoDB sebagian akan menggunakan indeks. Biasanya, komputer kueri akan menerapkan kesetaraan dan rentang filter terlebih dahulu dan menggunakan indeks. Setelah menerapkan filter ini, mesin kueri dapat mengevaluasi filter tambahan dan menggunakan pemuatan dokumen yang tersisa untuk menghitung agregat, jika diperlukan.

Berikut contohnya:

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

Dalam hal ini, indeks dapat mengoptimalkan stage $match. Menambahkan indeks untuk foodGroup secara signifikan akan meningkatkan performa kueri. Seperti di MongoDB, Anda harus menempatkan $match sedini mungkin di saluran agregasi untuk memaksimalkan penggunaan indeks.

Dalam API Azure Cosmos DB untuk MongoDB, indeks tidak digunakan untuk agregasi aktual, yang dalam hal ini adalah $max. Menambahkan indeks version tidak akan meningkatkan performa kueri.

Kueri tempat jumlah dokumen yang diambil sama dengan Jumlah Dokumen Output

Jika retrievedDocumentCount kira-kira sama dengan outputDocumentCount, mesin kueri tidak harus memindai banyak dokumen yang tidak perlu.

Mengecilkan kueri lintas partisi

Azure Cosmos DB menggunakan partisi untuk menskalakan masing-masing kontainer karena Unit Permintaan dan kebutuhan penyimpanan data meningkat. Setiap partisi fisik memiliki indeks yang terpisah dan independen. Jika kueri Anda memiliki filter kesetaraan yang cocok dengan kunci partisi kontainer, Anda hanya perlu memeriksa indeks partisi yang relevan. Pengoptimalan ini mengurangi jumlah total RUs yang diperlukan kueri. Pelajari selengkapnya tentang perbedaan antara kueri dalam partisi dan kueri lintas partisi.

Jika Anda memiliki sejumlah besar RUs yang disediakan (lebih dari 30.000) atau sejumlah besar data yang disimpan (lebih dari sekitar 100 GB), Anda mungkin memiliki wadah yang cukup besar untuk melihat pengurangan biaya RU kueri yang signifikan.

Anda dapat memeriksa array shardInformation untuk memahami metrik kueri untuk setiap partisi fisik individual. Jumlah nilai shardKeyRangeId unik adalah jumlah partisi fisik di mana kueri perlu dijalankan. Dalam contoh ini, kueri dijalankan pada empat partisi fisik. Penting untuk dipahami bahwa eksekusi sepenuhnya independen dari pemanfaatan indeks. Dengan kata lain, kueri lintas-partisi masih dapat menggunakan indeks.

  "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
                    }
                ]

Pengoptimalan yang mengurangi latensi kueri

Dalam banyak kasus, biaya RU mungkin dapat diterima ketika latensi kueri masih terlalu tinggi. Bagian berikut ini memberikan gambaran umum tips untuk mengurangi latensi kueri. Jika Anda menjalankan kueri yang sama beberapa kali pada set data yang sama, biasanya akan memiliki biaya RU yang sama setiap kali. Tetapi latensi kueri mungkin berbeda di antara eksekusi kueri.

Meningkatkan kedekatan

Kueri yang dijalankan dari wilayah yang berbeda dari akun Azure Cosmos DB akan memiliki latensi yang lebih tinggi daripada jika dijalankan di dalam wilayah yang sama. Misalnya, jika Anda menjalankan kode di komputer desktop, Anda harus mengharapkan latensi menjadi puluhan atau ratusan milidetik lebih tinggi (atau lebih) daripada jika kueri berasal dari mesin virtual dalam wilayah Azure yang sama dengan Azure Cosmos DB. Sangat mudah untuk mendistribusikan data secara global di Azure Cosmos DB untuk memastikan Anda dapat membawa data Anda lebih dekat ke aplikasi Anda.

Meningkatkan throughput yang disediakan

Di Azure Cosmos DB, throughput yang disediakan diukur dalam Unit Permintaan (RUs). Bayangkan Anda memiliki kueri yang menggunakan 5 RUs throughput. Misalnya, jika Anda menyediakan 1.000 RUs, Anda akan dapat menjalankan kueri tersebut 200 kali per detik. Jika Anda mencoba menjalankan kueri ketika tidak ada cukup throughput yang tersedia, Azure Cosmos DB akan membatasi permintaan. API Azure Cosmos DB untuk MongoDB akan secara otomatis mencoba kembali kueri ini setelah menunggu beberapa saat. Permintaan yang dicekik membutuhkan waktu lebih lama, sehingga meningkatkan throughput yang disediakan dapat meningkatkan latensi kueri.

estimatedDelayFromRateLimitingInMilliseconds nilai memberikan rasa potensi manfaat latensi jika Anda meningkatkan throughput.

Langkah berikutnya