استكشاف مشكلات الاستعلام وإصلاحها عند استخدام Azure Cosmos DB ل MongoDB

ينطبق على: MongoDB

توضح هذه المقالة نهجاً عاماً موصى به فيما يتعلق باستكشاف أخطاء الاستعلامات في Azure Cosmos DB وإصلاحها. وعلى الرغم من أنه ينبغي عليك عدم اعتبار الخطوات الموضّحة في هذه المقالة بمثابة حماية كاملة من مشكلات الاستعلام المحتملة، فقد أدرجنا بها نصائح الأداء الأكثر شيوعاً. يجب استخدام هذه المقالة كمنطلق لاستكشاف أخطاء الاستعلامات البطيئة أو باهظة الثمن وإصلاحها في واجهة برمجة تطبيقات Azure Cosmos DB لـ MongoDB. إذا كنت تستخدم Azure Cosmos DB ل NoSQL، فراجع مقالة دليل استكشاف أخطاء استعلام API for NoSQL وإصلاحها.

يتم تصنيف تحسينات الاستعلام في Azure Cosmos DB بشكل عام على النحو التالي:

  • تحسينات تُقلل من تكلفة وحدة الطلب (RU) للاستعلام
  • تحسينات تُقلل من زمن الانتقال فقط

إذا قللت تكلفة وحدة الطلب لاستعلام ما، فسوف تقلل عادةً زمن الانتقال أيضاً.

تُقدم هذه المقالة أمثلة يمكنك إعادة إنشائها باستخدام مجموعة بيانات التغذية.

إشعار

تفترض هذه المقالة أنك تستخدم واجهة برمجة تطبيقات Azure Cosmos DB لحسابات MongoDB مع الإصدار 3.6 والإصدارات الأحدث. وجدت بعض الاستعلامات التي أُجريَت بشكل سيئ في الإصدار 3.2 تحسينات مهمة في الإصدارات 3.6+. ينبغي الترقية إلى الإصدار 3.6 عن طريق تقديم طلب دعم.

استخدام أمر $explain للحصول على المقاييس

عند تحسين استعلام في Azure Cosmos DB، تكون الخطوة الأولى دائماً هي الحصول على تكلفة وحدة الطلب (RU) لاستعلامك. كمبدأ توجيهي، يجب الوصول إلى طرق لخفض تكلفة وحدة الطلب (RU) للاستعلامات التي تزيد تكلفتها عن 50 وحدة طلب.

وبالإضافة إلى الحصول على تكلفة وحدة الطلب (RU)، يجب استخدام الأمر $explain للحصول على الاستعلام ومقاييس استخدام الفهرس. فيما يلي مثال عن تشغيل استعلام واستخدام الأمر $explain لعرض مقاييس استخدام الفهرس والاستعلام:

الأمر $explain:

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

إخراج:

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

نتيجة الأمر $explain طويلة وتحتوي على معلومات مفصلة عن تنفيذ الاستعلام. بشكل عام، هناك بعض الأقسام التي يجب التركيز عليها عند تحسين أداء الاستعلام:

مقياس ‏‏الوصف
timeInclusiveMS وقت استجابة الاستعلام الخلفية
pathsIndexed يظهر الفهارس التي استخدمها الاستعلام
pathsNotIndexed عرض الفهارس التي يمكن أن يكون استخدمها الاستعلام إن كانت متوفرة
shardInformation ملخص أداء الاستعلام لجزء فعلي معين
retrievedDocumentCount عدد المستندات التي حمّلها محرك الاستعلام
outputDocumentCount عدد المستندات التي تم إرجاعها في نتائج الاستعلام
estimatedDelayFromRateLimitingInMilliseconds وقت استجابة الاستعلام الإضافي المقدر بسبب تقييد المعدل

بعد الحصول على مقاييس الاستعلام، قارِّن retrievedDocumentCount بـ outputDocumentCount لاستعلامك. واستخدم هذه المقارنة لتحديد الأقسام ذات الصلة لمراجعتها في هذه المقالة. retrievedDocumentCount هو عدد المستندات التي يحتاج محرك الاستعلام إلى تحميلها. outputDocumentCount هو عدد المستندات المطلوبة لنتائج الاستعلام. إذا كان retrievedDocumentCount أكبر بكثير من outputDocumentCount، يعني هذا أن هناك جزءاً واحداً على الأقل من استعلامك غير قادر على استخدام فهرس ويحتاج إلى إجراء فحص.

راجع الأقسام التالية لفهم تحسينات الاستعلام ذات الصلة للسيناريو الخاص بك.

تكلفة وحدة طلب الاستعلام مرتفعة جداً

عدد المستندات التي تم استردادها أكبر بكثير من عدد مستندات الإخراج

عدد المستندات التي تم استردادها يساوي تقريباً عدد مستندات الإخراج

تكلفة وحدة طلب الاستعلام مقبولة ولكن زمن الانتقال لا يزال مرتفعاً جداً

الاستعلامات حيث يتجاوز عدد المستندات المستردة عدد مستند الإخراج

retrievedDocumentCount هو عدد المستندات التي يحتاج محرك الاستعلام إلى تحميلها. outputDocumentCount هو عدد المستندات التي أنتجها الاستعلام. إذا كان retrievedDocumentCount أكبر بكثير من outputDocumentCount، يعني هذا أن هناك جزءاً واحداً على الأقل من استعلامك غير قادر على استخدام فهرس ويحتاج إلى إجراء فحص.

يرد فيما يلي مثال على استعلام المسح الضوئي الذي لم يتم تقديمه بالكامل من قبل الفهرس:

الأمر $explain:

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

إخراج:

{
    "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) أعلى بكثير من outputDocumentCount (1)، مما يعني أن هذا الاستعلام يتطلب فحص المستند.

إدراج الفهارس الضرورية

يجب عليك فحص صفيف pathsNotIndexed وإضافة هذه الفهارس. في هذا المثال، يجب فهرسة المسارين foodGroup وdescription.

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

تختلف أفضل ممارسات الفهرسة في API Azure Cosmos DB لـ MongoDB عن MongoDB. في واجهة برمجة تطبيقات Azure Cosmos DB لـ MongoDB، تُستخدم الفهارس المركبة فقط في الاستعلامات التي تحتاج إلى الفرز بكفاءة حسب الخصائص المتعددة. إذا كانت لديك استعلامات مع عوامل تصفية في خصائص متعددة، فيجب عليك إنشاء فهارس حقل واحد لكل خاصية من هذه الخصائص. يمكن أن تستخدم مسندات الاستعلام عدة فهارس حقل مفرد.

يمكن أن تسهِّل فهارس أحرف البدل عملية الفهرسة. خلافاً لما يحدث في MongoDB، يمكن أن تدعم فهارس البدل حقول متعددة في دوال تقييم الاستعلام. لن يكون هناك اختلاف في أداء الاستعلام إذا كنت تستخدم فهرس بدل واحد بدلاً من إنشاء فهرس منفصل لكل خاصية. تعد إضافة فهرس حرف بدل لجميع الخصائص أسهل طريقة لتحسين جميع استعلاماتك.

يمكنك إضافة فهارس جديدة في أي وقت دون التأثير في قابلية الوصول إلى الكتابة أو القراءة. يُمكنك تعقب التقدم المحرز في تحويل الفهرس.

معرفة عمليات التجميع التي تستخدم الفهرس

في معظم الحالات، ستستخدم عمليات التجميع في واجهة برمجة تطبيقات Azure Cosmos DB لـ MongoDB جزئياً الفهارس. عادةً ما يطبق محرك الاستعلام عوامل تصفية النطاق والمساواة أولاً ويستخدم الفهارس. بعد تطبيق عوامل التصفية هذه، يُمكن لمحرك الاستعلام تقييم عوامل التصفية الإضافية واللجوء إلى تحميل المستندات المتبقية لحساب التجميع، إذا لزم الأمر.

إليك مثال:

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

في هذه الحالة، يمكن أن تحسِّن الفهارس المرحلة $match. ستؤدي إضافة فهرس لـ foodGroup إلى تحسين أداء الاستعلام بشكل ملحوظ. كما هو الحال في MongoDB، يجب وضع $match في أقرب وقت ممكن في مسار التجميع لزيادة استخدام الفهارس إلى أقصى حد.

في واجهة برمجة تطبيقات Azure Cosmos DB لـ MongoDB، لا تُستخدَم الفهارس في التجميع الفعلي الذي يكون في هذه الحالة $max. لن تؤدي إضافة فهرس في version إلى تحسين أداء الاستعلام.

الاستعلامات التي يتساوى فيها عدد المستندات المُسترَدة مع عدد المستندات الناتجة

إذا كان retrievedDocumentCountيساوي outputDocumentCount تقريباً، فلن يضطر محرك الاستعلام إلى فحص العديد من المستندات غير الضرورية.

تصغير الاستعلامات عبر الأقسام

تستخدم Azure Cosmos DB التقسيم لتغيير حجم الحاويات الفردية عندما يتعين زيادة وحدة الطلب وتخزين البيانات. يحتوي كل قسم فعلي على فهرس منفصل ومستقل. إذا كان استعلامك يحتوي على أحد عوامل تصفية المساواة التي تُطابق مفتاح تقسيم الحاوية، فسيتعين عليك التحقق من فهرس القسم ذي الصلة فقط. يقلل هذا التحسين العدد الإجمالي لوحدات الطلب الذي يتطلب الاستعلام. تعرّف على المزيد حول الاختلافات بين الاستعلامات داخل القسم والاستعلامات عبر الأقسام.

إذا كان لديك عدد كبير من وحدات الطلب المتوفرة (أكثر من 30,000) أو كمية كبيرة من البيانات المخزنة (أكثر من 100 غيغابايت تقريباً)، فمن المحتمل أن تكون لديك حاوية كبيرة بما يكفي لرؤية انخفاض كبير في تكاليف وحدة طلب الاستعلام.

يمكنك التحقق من صفيف shardInformation لفهم مقاييس الاستعلام لكل قسم فعلي فردي. عدد قيم shardKeyRangeId الفريدة هو عدد الأقسام الفعلية المطلوب تنفيذ الاستعلام فيها. في هذا المثال، نُفِذ الاستعلام في أربعة أقسام فعلية. من المهم أن نفهم أن التنفيذ مستقل تماماً عن استخدام الفهرس. بمعنى آخر، لا يزال بإمكان الاستعلامات عبر الأقسام استخدام الفهارس.

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

تحسينات تُقلل من زمن انتقال الاستعلام

في كثير من الحالات، قد تكون تكلفة وحدة الطلب مقبولة عندما يكون زمن انتقال الاستعلام مرتفعاً جداً. تعرض الأقسام التالية نظرة عامة عن تلميحات لتقليل زمن انتقال الاستعلام. في حالة تشغيل الاستعلام نفسه عدة مرات في مجموعة البيانات نفسها، عادةً ما تكون تكلفة وحدة الطلب هي نفسها في كل مرة. ولكن قد يختلف زمن انتقال الاستعلام بين عمليات تنفيذ الاستعلام.

تحسين التقارب

سيكون زمن انتقال الاستعلامات التي تُشغَل من منطقة مختلفة عن حساب Azure Cosmos DB أعلى من زمن انتقال الاستعلامات التي تُشغَل داخل المنطقة نفسها. على سبيل المثال، في حالة تشغيل تعليمة برمجية على كمبيوتر سطح المكتب، ينبغي أن تتوقع أن يكون زمن الانتقال أعلى بعشرات أو مئات المللي ثانية (أو أكثر) من زمن انتقال الاستعلام الصادر من جهاز ظاهري داخل منطقة Azure نفسها لخدمة Azure Cosmos DB. إن توزيع البيانات بشكل عمومي في Azure Cosmos DB لضمان إمكانية نقل بياناتك إلى تطبيقك أمر سهل.

زيادة معدل النقل المتوفر

في Azure Cosmos DB، يُقاس معدل النقل المخصص بوحدات الطلب (RU). تخيل أن لديك استعلاماً يستهلك 5 وحدات طلب من معدل النقل. على سبيل المثال، في حالة توفير 1000 وحدة طلب، يُمكنك تشغيل هذا الاستعلام 200 مرة في الثانية. إذا حاولت تشغيل الاستعلام عندما لم يكن هناك ما يكفي من الإنتاجية المتاحة، فسيحد Azure Cosmos DB الطلبات. ستقوم واجهة برمجة تطبيقات Azure Cosmos DB لـ MongoDB بإعادة محاولة هذا الاستعلام تلقائياً بعد الانتظار لفترة قصيرة. تستغرق الطلبات المقيدة وقتاً أطول، لذا يُمكن أن تؤدّي زيادة معدل النقل المتوفر إلى تحسين زمن انتقال الاستعلام.

تعطي القيمة estimatedDelayFromRateLimitingInMilliseconds تلميحاً بفوائد زمن الانتقال المحتملة في حالة زيادة معدل النقل.

الخطوات التالية