با اینکه دیتابیس MongoDB یکی از سریع ترین دیتابیس های NOSQL هست اما این ویژگی به تنهایی کافی نبوده و یه کوئری پیچیده میتونه کد شما رو با مشکل روبرو کنه. همه ما روزانه با این سوال مواجه هستیم که وقتی اپلیکیشن به صورت ناپایدار کار میکنه کجا و به دنبال چه چیزی باید باشیم؟ امیدوارم این چند کار ساده بتونه از دردسرهای روزانه کار با دیتابیس مونگو کم کنه.
به صورت پیش فرض، مونگو تمام کوئری های با زمان بالای 100 میلی ثانیه رو لاگ میکنه، محل این لاگ تو تنظیمات Configuration مشخص می شه که به صورت نرمال تو مسیر زیر قرار داره.
/var/log/mongodb/mongod.log
برای تغییر مسیر لاگ به فایل mongod.conf مراجعه کنین و مسیر نگهداری فایل رو تغییر بدین.
برای تنظیمات بیشتر لاگ log level verbosity و تنظیم مواردی که نیاز به پروفایل دارین به profiling parameters مراجعه کنید.
احتمال داره لاگ فایل بزرگ شده باشه پس شما میتونید قبل از پروفایل کردن اون رو Clear کنین.
با دستور زیر می تونین فایل لاگ رو خالی کنین:
db.runCommand({ logRotate : 1 });
با این روش فایل لاگ جدیدی ایجاد میشه و دیتا لاگ قبلی با تاریخ Backup ذخیره میشه.
می تونیم فایل بک آپ رو پاک کنیم یا به مسیر دیگه ای منتقل کنیم.
با دستور زیر میتونیم پروفایلر مونگو رو تنظیم کنیم که کوئری رو مثلا با زمان اجرا بیش از 10 ثانیه لاگ کنه :
db.setProfilingLevel(1, { slowms: 10000 })
برای بررسی لاگ و پیدا کردن کوئری های دردسرساز کلمه 'COMMAND' رو جستجو کنین تا کوئری اجرا شده به همراه زمان در واحد میلی ثانیه رو در انتهای کوئری پیدا کنین. برای مثال :
2016-02-12T11:05:08.161+0000 I COMMAND
[conn563] command project.$cmd command: count { count: "test", query: { published: { $ne: false }, country: "uk" } } planSummary: IXSCAN { country: 1 } keyUpdates:0 writeConflicts:0 numYields:31 reslen:44 locks: { Global: { acquireCount: { r: 64 } }, MMAPV1Journal: { acquireCount: { r: 32 } }, Database: { acquireCount: { r: 32 } }, Collection: { acquireCount: { R: 32 } } } 403ms
این میتونه بهتون کمک کنه تا کوئری های کند رو که باعث کاهش performance دیتابیس شدن رو پیدا کنین.
مثل بقیه دیتابیس ها مونگو هم امکان explain facility برای کوئری رو فراهم میکنه تا بفهمیم دیتابیس چطور عمل میکنه. با اضافه کردن دستور زیر به انتهای کوئری میتونین نحوه اجرا اون رو ببینید.
explain('executionStats')
برای مثال :
db.user.find( { country: 'AU', city: 'Melbourne' } ).explain('executionStats');
یا به صورت :
db.user.explain('executionStats').find( { country: 'AU', city: 'Melbourne' } );
این کوئری یک json طولانی رو برمیگردونه اما میتونین با دستورای زیر بخش کلیدی رو استخراج کنین:
تعداد داکیومنت ها برگردانده میشه
تعداد داکیومنت هایی که اسکن شدن تا نتیجه بدست بیاد.
اگه تعداد داکیومنت های بررسی شده خیلی بزرگتر از تعداد برگردونده شده باشه، پس کوئری بهینه نیست. در بدترین حالت، مونگو مجبور خواهد بود تمام داکیومنت های داخل collection رو بررسی کنه . همچنین کوئری باید از ایندکس هم استفاده کرده باشه.
برای اطلاعات بیشتر به همراه مثال به لینک های Analyze Query Performance و db.collection.explain() در MongoDB manual مراجعه کنید.
دیتابیس های NoSQL هم به ایندکس نیاز دارن مثل دیتابیس های رابطه ای . یک ایندکس از مجموعه یک یا چند فیلد درست میشه تا سرعت اجرا کوئری رو زیاد کنه. برای مثال ، شما میتونین فیلد country در کالکشن user رو ایندکس کنین. موقعی که کوئری بدنبال 'AU' میگرده، مونگو میتونه سراغ ایندکسش بره و نتایج مناسب رو برگردونه بدون نیاز به اسکن کردن کالکشن user.
ایندکس با دستور CreateIndex در مونگو ساخته میشه ، برای مثال برای کالکشن user :
db.user.createIndex({ country: 1 });
همچنین میتونیم ایندکس با ترکیب چندین فیلد درست کنیم :
db.user.createIndex({ country: 1, city: 1 });
چندین امکان مختلف هنگام ساخت ایندکس وجود داره که با مراجعه به راهنمای Index Introduction MongoDB میتونین مطالعه بیشتری روش داشته باشین .
معمولا دوست داریم نتایج کوئری رو به صورت مرتب ببینیم، برای مثال برای مشاهده تمام کاربران براساس کشور اونها :
db.user.find().sort({ country: 1 });
مرتب سازی موقعی خوب عمل میکنه که ایندکس مورد نیاز برروی اون فیلد وجود داشته باشه .
اگر ایندکسی روی اون فیلد نساخته باشین مونگو مجبوره خودش لیست رو مرتب کنه و این موقعی که لیست بزرگی از داکیومنت ها در حال بررسی هست مشکل ساز میشه. دیتابیس حداکثر 32MB مموری برای اینکار اختصاص میده. ( 32MB memory limit on sorting operations ) و اگر فرآیند مرتب سازی بیش از این رم لازم داشته باشه، هیچ نتیجه ای برگردونده نمیشه و معمولا 1000 داکیومنت نسبتا کوچیک هم برای رسیدن به این سقف کافی خواهد بود.
حالا فرض کنین ایندکس مورد نیاز رو روی Country ایجاد کرده باشیم.
db.user.createIndex({ country: 1 });
اما کوئری اجرا شده شامل sort برروی فیلدهای City , Country باشه.
db.user.find().sort({ country: 1, city: 1 });
درحالی که ایندکس مورد نیاز برای country روی دیتابیس هست، اما مونگو مجبوره فیلد City رو خودش مرتب کنه .
این فرآیند کنده و ممکنه به 32MB سقف مموری Sorting برسه. پس لازمه که ایندکس ترکیبی از دو فیلد رو برای این کوئری ایجاد کنیم .
db.user.createIndex({ country: 1, city: 1 });
حالا ایندکس مناسبی برای کوئری وجود داره و میتونه با سرعت مناسبی اجرا بشه. همچنین میتونه کوئری با ترتیب برعکس رو هم به خوبی اجرا کنه، چون از انتهای ایندکس شروع خواهد کرد .
db.user.find().sort({ country: -1, city: -1 });
اما اگه بخواهین فقط یک فیلد رو با ترتیب برعکس انتخاب کنین به مشکل میخوره و نیاز به ساخت ایندکس مناسب برای همین کوئری خواهد بود .
db.user.find().sort({ country: -1, city: 1 });
ایندکس مورد نیاز :
db.user.createIndex({ country: -1, city: 1 });
کوئری های مونگو تا زمانی که لازم باشه اجرا می شن (البته در بعضی IDE ها سقف پیش فرض وجود دارد) و در صورتی که کوئری کند و نامناسب باشه میتوانه سرور رو دچار مشکل کنه. شما می تونین برای کوئری که به مونگو میفرستین این محدودیت رو بزارین تا بعد از زمان تعیین شده timeout بده و بیش از این سرور رو درگیر نکنه. برای مثال می تونین سقف 100 milliseconds رو برای کوئری اعمال کنید .
db.user.find({ city: /^A.+/i }).maxTimeMS(100);
در این کوئری فیلد City بررسی می شه تا تمام مواردی رو که شامل حرف A هست انتخاب کنه. زمانی که برای حداکثر تایم انتخاب می کنیم باید معقول باشه. متاسفانه مونگو اجازه نمیده این مقدار رو به صورت سراسری تنطیم کنیم و باید برای هر کوئری جداگانه ست بشه.
اگر احساس کردین با وجود ایندکس مناسب کوئری کند اجرا می شه نیاز هست که rebuilding indexes رو برای هر کالکشن اجرا کنید. برای مثال برای کالکشن User دستور زیر رو اجرا می کنیم.
db.user.reIndex();
در مواقعی ممکنه فرآیند Fail بشه که در اینصورت نیاز هست database repair رو انجام بدین و همیشه توصیه میشه که با استفاده از mongodump فایل پشتیبانی از دیتابیس خودتون تهیه کنید.
منبع :