یک plan چطور رد (reject) می‌شود؟

?How Does a Plan Reject

24 اردیبهشت 1401
درسنامه درس 56 از سری دوره جامع آموزش MongoDB
MongoDB: یک plan چطور رد (reject) می شود؟ (قسمت 58)

همانطور که می دانید plan های مختلفی برای اجرای یک کوئری وجود دارد. plan به معنی «نقشه» یا «طرح» است و منظور ما از وجود plan های مختلف، وجود روش های مختلف برای اجرای کوئری ها است. مثلا اگر یک کوئری find داشته باشیم، می توانیم collection scan انجام بدهیم، می توانیم index scan انجام بدهیم و الی آخر. در این قسمت می خواهیم روی این موضوع تمرکز کنیم تا بفهمیم MongoDB چطور یک plan را قبول را رد می کند.

من از کالکشن customers خودم استفاده می کنم و ابتدا تمام ایندکس های آن را بررسی می کنم:

db.customers.getIndexes()

این کوئری به من می گوید که دو ایندکس داریم. یکی برای id_ و دیگری برای name (در جلسه قبل آن را تعریف کردیم):

"v" : 2,                         
 "key" : {                        
         "_id" : 1                
 },                               
 "name" : "_id_",                 
 "ns" : "contactData.customers"   
                                  
 "v" : 2,                         
 "key" : {                        
         "name" : 1               
 },                               
 "name" : "name_1",               
 "ns" : "contactData.customers"   

حالا یک compound index جدید را برای این کالکشن می سازیم (هنوز اجرا نکنید):

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

توجه داشته باشید که در این کوئری، ترتیب مهم است. اگر name را اول قرار می دادیم، دیگر ایندکس قبلی ما (که فقط برای name بود) بی معنی می شد. چرا؟ به کد زیر توجه کنید (اجرا نکنید):

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

همین چند لحظه پیش به شما گفتم که ما دو ایندکس داریم که یکی از آن ها برای name بود که یک single field index است (ایندکس تکی) و از طرفی در کوئری بالا یک compound index را تعریف می کنیم که اولین فیلد آن name است. ما در جلسات قبل یاد گرفتیم که در ایندکس های ترکیبی، ایندکس ها از چپ به راست استفاده می شوند بنابراین می توانیم از مقدار آن ها به صورت مستقل استفاده کنیم و نیازی به یک ایندکس جداگانه برای name نخواهیم داشت!

با این حساب کوئری زیر را اجرا کنید:

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

حالا باید به دنبال فردی بگردیم که name و age دارند تا نکته جالبی را از آن پیدا کنیم:

db.customers.explain().find({name: "Amir", age: 24})

همانطور که می دانید، هنگام اجرای یک کوئری، ترتیب اهمیتی ندارد بلکه فقط در زمان تعریف خود ایندکس مهم است. گزارشی که از کوئری بالا به ما داده می شود شامل winningPlan است که مقدار "IXSCAN" را دارد (یعنی از ایندکس ها به درستی استفاده شده است و collection scan نداشته ایم). نکته جالب تر این است که "rejectedPlans" نیز برای این کوئری گزارش شده است که به شکل زیر می باشد:

"rejectedPlans" : [                                           
         {                                                     
                 "stage" : "FETCH",                            
                 "inputStage" : {                              
                         "stage" : "IXSCAN",                   
                         "keyPattern" : {                      
                                 "age" : 1,                    
                                 "name" : 1                    
                         },                                    
                         "indexName" : "age_1_name_1",         
                         "isMultiKey" : false,                 
                         "multiKeyPaths" : {                   
                                 "age" : [ ],                  
                                 "name" : [ ]                  
                         },                                    
                         "isUnique" : false,                   
                         "isSparse" : false,                   
                         "isPartial" : false,                  
                         "indexVersion" : 2,                   
                         "direction" : "forward",              
                         "indexBounds" : {                     
                                 "age" : [                     
                                         "[24.0, 24.0]"        
                                 ],                            
                                 "name" : [                    
                                         "[\"Amir\", \"Amir\"]"
                                 ]                             
                         }                                     
                 }                                             
         }                                                     
 ]                                                             

طرح رد شده این بوده است که از compound index ما استفاده شود و طرح برنده استفاده از ایندکس name (ایندکس تکی) بوده است. MongoDB بر اساس تحلیل خودش انتخاب می کند که کدام نقشه و طرح سریع تر و بهتر است اما فرآیند انتخاب این طرح ها چگونه است؟

در قدم اول MongoDB به دنبال ایندکس هایی می گردد که برای کوئری ما قابل استفاده باشد. در مثال بالا هر دو ایندکس name و  age_1_name_1 به درد کوئری ما می خوردند و می توانستیم از هر دو استفاده کنیم. مثلا فرض کنید کوئری داشته باشیم که به دنبال 100 سند (document) بگردد و سه طرح یا plan مختلف برای دست یابی به این 100 سند داشته باشیم. MongoDB اجازه می دهد که این 3 روش با هم رقابت کنند تا مشخص شود کدام یک سریع تر این 100 سند را پیدا خواهد کرد (البته این سه روش به طور کامل اجرا نمی شوند و داده ها را برنمی گردانند - بحث فقط پیدا کردن 100 سند از روی ایندکس یا هر چیز دیگری است). طبیعتا یکی از این روش ها از دو روش دیگر سریع تر خواهد بود و 100 سند را قبل از بقیه پیدا می کند بنابراین MongoDB آن را انتخاب کرده و به عنوان plan برنده اجرا می کند.

این مسابقه بین 3 روش، نیاز به منابع و زمان دارد تا اجرا شود (گرچه که بسیار کم است) بنابراین نباید در هر بار (مثلا با هر بار اجرای یک کوئری find) دوباره مسابقه برگزار کنیم. به همین دلیل MongoDB طرح برنده را دقیقا برای کوئری شما کش می کند (فقط و فقط برای همان کوئری اجرا شده) و در آینده برای هر کوئری که دقیقا برابر کوئری قبلی باشد، از همان نتیجه کش شده استفاده می کند. البته این کش تا ابد ذخیره نمی ماند و پس از insert شدن تعداد خاصی سند (در زمان نوشتن این مقاله، 1000 سند) از بین می رود. اگر index ها دوباره ساخته شوند باز هم این کش از بین می رود. اگر ایندکس جدیدی به مجموعه ایندکس ها اضافه شود، این کش از بین می رود. زمانی که سرور MongoDB ریستارت شود، باز هم این کش حذف می شود.

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

db.customers.dropIndexes()

حالا دوباره دستورات زیر را اجرا می کنم تا همان index های قبلی را دوباره بسازیم:

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

سپس دوباره کوئری خود را اجرا می کنم:

db.customers.explain().find({name: "Amir", age: 24})

این بار طرح برنده، استفاده از ایندکس compound و طرح reject شده استفاده از ایندکس تکی name است! چرا؟ در کوئری بالا هم به دنبال name و هم به دنبال age می گردیم بنابراین ایندکس صحیح تری نسبت به name است. چرا قبلا این اتفاق نیفتاد؟ به دلیل اینکه روش بهتر (استفاده از name) قبلا کش شده بود و مسابقه ای اجرا نمی شد تا ایندکس سریع تر شناسایی شود. با حذف و ایجاد دوباره ایندکس ها توانستیم طرح بهتر را شناسایی کنیم.

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

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

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