عیب‌یابی کوئری‌ها و آشنایی با covered queries

Troubleshooting Queries and Getting Acquainted with Covered Queries

24 اردیبهشت 1401
درسنامه درس 55 از سری دوره جامع آموزش MongoDB
MongoDB: عیب یابی کوئری ها و آشنایی با covered queries (قسمت 57)

در قسمت های قبل از این فصل، در مورد انواع ایندکس ها و نحوه ساخت آن ها صحبت کردیم اما می خواهیم در این جلسه به نکات مهم تری بپردازیم. به طور مثال چطور کوئری های خود را طراحی کنیم که بهینه باشند؟ آیا باید برای کوئری های خود نقشه خاصی داشته باشیم؟ اگر کوئری های ما به مشکل خوردند، چطور می توانیم آن ها را عیب یابی کرده و متوجه مشکل آن ها شویم؟

برای اینکه متوجه مشکلات کوئری خود بشویم (فرآیند عیب یابی)، می توانیم از متد explain استفاده کنیم. ما تا این جلسه ده ها بار از این متد استفاده کرده ایم تا نحوه اجرای آن و زمان لازم برای اجرایش را مشاهده کنیم. حالت پیش فرض explain همان حالت خلاصه است که بدون پاس دادن آرگومان دریافت می کنیم. مثلا:

db.contacts.explain().find({"dob.age": {$gt: 60}, gender: "male"})

زمانی که آرگومانی به explain پاس داده نمی شود، از مقدار پیش فرض استفاده خواهیم کرد که queryPlanner است. یعنی کوئری بالا با کوئری زیر دقیقا یکسان هستند:

db.contacts.explain("queryPlanner").find({"dob.age": {$gt: 60}, gender: "male"})

گزارشی که از این نوع دستور می گیرید، فقط شامل winningPlan و خلاصه ای کلی از کوئری می باشد و اطلاعات مهم دیگری نمی گیریم. روش دیگر استفاده از explain، پاس دادن executionStats می باشد که قبلا با آن آشنا شده ایم:

db.contacts.explain("executionStats").find({"dob.age": {$gt: 60}, gender: "male"})

با این کار گزارش کامل تری دریافت خواهید کرد که شامل جزئیات اجرای کوئری، winningPlan و همچنین rejectedPlans می باشد. آرگومان سومی که می توانید به Explain بدهید، مقدار allPlansExecution می باشد:

db.contacts.explain("allPlansExecution").find({"dob.age": {$gt: 60}, gender: "male"})

این کوئری کامل ترین گزارش را به شما می دهد که شامل تمام موارد ذکر شده در قبل می باشد اما فرآیند انتخاب winningPlan را نیز برایتان توضیح می دهد. مثلا چطور MongoDB تصمیم به اجرای collection scan گرفته است و از index scan استفاده نکرده است. در طول این دوره با تمام این موارد بیشتر کار خواهیم کرد.

برای اینکه بفهمیم آیا کوئری ما بهینه است و از سرعت کافی برخوردار است، باید با explain گزارشی از کوئری خود در دو حالت collection scan و index scan بگیرید. سپس مقدار "executionTimeMillis" (زمان لازم برای اجرای کوئری) در هر دو گزارش را با یکدیگر مقایسه کنید تا ببینید آیا در کوئری خاص شما، index ها سریع تر از حالت عادی هستند یا خیر. در مرحله بعد باید مقدار "totalKeysExamined" را در هر دو گزارش بررسی کنید (تعداد کل key های بررسی شده در لیست index برای تکمیل کوئری شما) تا ببینید مثلا برای دریافت 50 سند، چه تعدادی از key های لیست ایندکس بررسی و اسکن شده اند. همچنین مقادیر "totalDocsExamined" (تعداد کل اسناد بررسی شده برای تکمیل کوئری شما) و "nReturned" (تعداد اسناد برگردانده شده) را نیز در هر دو حالت عادی و index شده مقایسه کنید. قانون کلی برای این بررسی به شکل زیر است:

  • مقدار totalKeysExamined باید تا حد ممکن به مقدار totalDocsExamined نزدیک باشد. این مسئله نشان می دهد که index ما جلوی اسکن شدن بیهوده تعداد زیادی از اسناد را گرفته است و فقط اسنادی اسکن شده اند که در لیست ایندکس بوده اند.
  • مقدار totalDocsExamined باید تا حد ممکن به مقدار nReturned نزدیک باشد. این مسئله نشان می دهد که کوئری ما صریح است و دقیقا همان اسنادی را بررسی کرده ایم که می خواستیم برگردانیم (نه بیشتر و نه کمتر).
  • یا تعداد اسناد بررسی شده (totalDocsExamined) صفر باشد. چطور؟ این مسئله مربوط به نوع خاصی از کوئری ها به نام covered query می باشد که هنوز با آن ها آشنا نشده ایم.

covered queries چیست؟

برای درک این مفهوم باید یک کالکشن جدید بسازیم:

db.customers.insertMany([{name: "Amir", age: 24, salary: 2000}, {name: "Pooya", age: 25, salary: 3000}])

سپس یک index را برای فیلد name به این کالکشن اضافه می کنیم:

db.customers.createIndex({name: 1})

حالا یک کوئری ساده می زنیم تا کاربر Amir را پیدا کنیم:

db.customers.explain("executionStats").find({name: "Amir"})

نتیجه این کوئری بدین شکل است که مقادیر "totalKeysExamined" و "totalDocsExamined" و "nReturned" روی عدد 1 هستند بنابراین یک سند بررسی شده است که آن هم ایندکس شده بوده (اولین ایندکس در لیست ایندکس ها) و همان یک سند نیز برگردانده شده است. ما در جلسه قبل گفتیم که totalDocsExamined ممکن است صفر بشود! یعنی تعداد اسناد بررسی شده صفر باشد! چطور ممکن است؟

اگر یادتان باشد گفتم که درون index ها یک pointer وجود دارد که به مکان یک سند در کالکشن اشاره می کند اما این تنها مقداری نیست که درون لیست ایندکس های ما قرار دارد. خود مقدارِ ایندکس شده نیز در لیست ایندکس ها وجود دارد که در مثال بالا همان رشته Amir می باشد! برای اینکه مستقیما به این مقدار دسترسی داشته باشیم باید یک projection را نیز به کوئری خود اضافه کنیم:

db.customers.explain("executionStats").find({name: "Amir"}, {_id: 0, name: 1})

اگر خودِ مقدارِ ایندکس شده درون لیست ایندکس وجود دارد بنابراین اصلا نیازی نیست برای دریافت name به collection نگاه کنیم بنابراین totalDocsExamined باید صفر شود اما مسئله اینجاست که فیلد id_ همیشه به صورت خودکار برگردانده می شود و id_ درون لیست ایندکس ها نیست (id_ یک فیلد جداگانه است که ایندکس جداگانه ای دارد) بنابراین در کوئری بالا با استفاده از projection گفته ایم که فقط فیلد name را برگردان اما id_ را برنگردان تا نیازی به رفتن به کالکشن نباشد. با اجرای این کوئری نتیجه زیر را در گزارش می بینیم:

"executionSuccess" : true,
"nReturned" : 1,
"executionTimeMillis" : 0,
"totalKeysExamined" : 1,
"totalDocsExamined" : 0,

همانطور که می بینید این کوئری آنقدر سریع است که executionTimeMillis آن (زمان لازم برای اجرای کوئری در واحد میلی ثانیه) برای من صفر شده است! این مسئله همیشه قابل اجرا نیست اما اگر در حالتی هستید که می توانید مثل من کوئری خود را طوری بنویسید که فقط از ایندکس چیزی بگیرد، حتما این کار را بکنید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش MongoDB توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

مقالات مرتبط
آخرین سوالات کاربران
5451218 در 3 سال قبل پرسیده:
ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو