دست به کد : تمرین عملی با مانگودیبی
ترم گذشته (ترم دوم سال تحصیلی ۹۹-۹۸) فرصت این را داشتم که به عنوان دستیار ارشد آموزشی دکتر اسدپور در درس کلانداده (دانشگاه تهران – ارائه شده برای دانشجویان تحصیلات تکمیلی)، مسوولیت اصلی طراحی تمرینها و پروژه نهایی درس را (با نظارت دکتر اسدپور) بر عهده داشته باشم. از بین سه سری تمرین اصلی، طراحی تمرین بخش دیتابیسهای NoSQL برعهده خودم بود ( و پروژه نهایی که در نوشتاری دیگر به طور مفصل به آن خواهم پرداخت ). تصمیم گرفتم سه قسمت مختلف این تمرین که برای کار با مانگودیبی، Neo4jو HBase طراحی شده بود را برای استفاده سایرین، به صورت عمومی منتشر کنم.
در اولین بخش از این سری سهگانه، به بررسی تمرین کار با دیتابیسهای سندگرا با محوریت مانگودیبی خواهیم پرداخت و به کمک گزارش چند نفر از دانشجویان، راه حل سوالات را هم ارائه خواهم کرد ( البته از این عزیزان، کسب اجازه شده است) . تمرین اصلی را میتوانید از این لینک، دانلود کنید.
مقدمه
در این تمرین عملی، فرض شده است که شما با مانگودیبی، آشنایی مقدماتی دارید و این دیتابیس سندگرای محبوب را نصب کرده اید. اگر احیاناً این پیش فرض برای شما صدق نمی کند، قبل از ادامه، به مقاله شروع به کار با مانگودیبی مراجعه کنید.
بررسی تمرین و خواسته مساله
در بخش اول تمرین، برای ذخیره اطلاعات کاربران یک سایت فرضی از مانگو استفاده میکنیم و بعد از ذخیره اطلاعات، با انجام چند پرسوجوی ساده، نحوه کار با این دیتابیس محبوب را فرا خواهیم گرفت.
قبل از شروع تمرین، بهتر است مروری بر موارد کاربرد مانگودیبی داشته باشیم. فرض کنید قصد طراحی سایتی برای آموزش مجازی و خرید و فروش دورهها را دارید. در این سایت، افراد میتوانند در دورههای مختلف ثبت نام کرده و با پرداخت هزینه، دورههای آموزشی را بگذرانند. مدرسان هم میتوانند دروس مختلف را تعریف و محتوای آموزشی آنها را بارگذاری کنند. این بخش از سامانه، با دیتابیسهای رابطهای مدیریت خواهد شد.
اما از آنجا که مدرسان سایت، رزومههای مختلفی خواهند داشت (دانشگاههای مختلف، مقاطع و رشتههای مختلف، مدارک غیر دانشگاهی، جوایز ، کنفرانسها ، مسوولیتهای اجرای و …)، میتوان کل اطلاعات سوابق کاربران را به صورت json و در دیتابیس مانگودیبی نگهداری کرد که انعطاف پذیری لازم برای ذخیره دادههای بدون ساختار و بسیار متغیر را داشته باشد.
برای برقراری ارتباط بین دو دیتابیس رابطهای و مانگودیبی، کلید جدول یوزر در دیتابیس رابطهای (یا یک فیلد منحصر بفرد دیگر مانند ایمیل یا کدملی)، باید در کالکشن متناظر در مانگودیبی ذخیره و ایندکس شود.
در این تمرین، فقط به ذخیره اطلاعات صد هزار کاربر در قالب جیسان در مانگودیبی و کار با این دادهها میپردازیم.
دریافت اطلاعات
برای ایجاد صد هزار کاربر از امکانات ایجاد کاربر تصادفی سایت randomuser.me استفاده میکنیم. این سایت که ملیت ایرانی را هم پشتیبانی میکند، از طریق API، یک تا پنج هزار کاربر تصادفی با ملیت و مشخصات داده شده تولید کرده، به ما در قالب جیسان برمیگرداند. نمونهای از دادههای تولید شده توسط این سایت را در زیر مشاهده میکنید:
کافی است همین جیسان دریافت شده را در مانگو دی بی ذخیره کنید (البته اطلاعات موجود در فیلد results و آن هم به ازای هر کاربر در یک سند جداگانه ذخیره خواهد شد.)
برای دریافت اطلاعات کاربران (که برای این تمرین حداقل نیاز به صد هزار کاربر خواهیم داشت)، میتوانید از کد پایتون زیر استفاده کنید (البته کد را باید اصلاح کنید که این تعداد کاربر دریافت و در مانگو ذخیره شود) :
دانلود مانگودیبی و ساخت کالکشن کاربران
مانگو دی بی را نصب کرده[۱] و کالکشن users را درون دیتابیس metadata (این دیتابیس هم باید ایجاد شود) بسازید. میتوانید از خط فرمان مانگودیبی یا ابزارهای گرافیکی رایج مانند MongoDB Compass یا Robo 3T[2] برای این منظور استفاده کنید.
کتاب کوچکThe Little MongoDB[3] میتواند راهنمای سریع شما برای کار با مانگو در این تمرین باشد.
گام اول تمرین
در گام اول، با فراخوانی آدرس https://randomuser.me/api/?nat=ir اطلاعات چندین کاربر تصادفی را دریافت و در مانگودیبی به صورت دستی ذخیره کنید و بررسی کنید چه فیلدهایی توسط خود مانگو به صورت خودکار، به دادهها افزوده میشود.
سپس با استفاده از کتابخانه pymongo[4] کدهای دریافت اطلاعات فوق را به گونهای تغییر دهید که همزمان با دریافت اطلاعات کاربران، آنها را در مانگو هم ذخیره کنید.
با دستور count، مطمئن شوید که صد هزار داده، دریافت شده باشند.
خروجی گام اول
نحوه ورود دستی دادهها در مانگو،کدهای نوشته شده برای درج اطلاعات و نحوه اطمینان از درج صدهزار سند در گزارش آورده شود.
گام دوم – دستورات اصلی
در گام دوم به دستورات پایه مانگودیبی میپردازیم و به سوالات زیر پاسخ میدهیم :
- نام و نام خانوادگی کاربرانی را پیدا کنید که بالای ۵۰ سال سن داشته و ساکن نیشابور باشند.
- قصد ارسال هدیه به کاربرانی داریم که بیش از بیست سال است که در سایت ما ثبت نام کرده اند. نام خانوادگی، آدرس و موبایل این کاربران را بیابید.
- تاریخ ثبت شده برای تولد(dob) و زمان ثبت نام به میلادی است. می خواهیم در کنار این تاریخها، یک فیلد جدید اضافه کنیم با نام year_persian که سال متناظر تقویم فارسی تولد و ثبت نام را هم داشته باشیم. از دستور update و یک فرمول ساده تبدیل سال میلادی به شمسی، استفاده کرده، این تغییر را اعمال کنید.
- قصد داریم در پایان هر روز، به تمام افرادی که امروز، روز تولدشان است، یک ایمیل ارسال کنیم و کد تخفیفی برای آنها بفرستیم. دستوری بنویسید که با اجرا شدن آن در هر روز، نام و نام خانوادگی و ایمیل ان اشخاص به ما برگردانده شود.
- ذخیره پسورد به شکل خام در اطلاعات کاربر، کاری غیرحرفهای است. میخواهیم این مشکل را برطرف کنیم. چه راه حلی برای حل مساله پیشنهاد میکنید؟ راه حل را ابتدا روی یک سند خاص، امتحان کنید و مطمئن شوید بعد از اعمال تغییرات، آن کاربر خاص را با داشتن یوزنیم و پسورد، میتوانید پیدا کنید. سپس تمام کاربران را به روز رسانی کنید.
خروجی گام دوم
دستورات نوشته شده ، خروجی و زمان اجرای هر کوئری
گام سوم – دستورات تجمعی و آماری (Aggregate Functions)
- میخواهیم یک کمپین تبلیغاتی برای سایت ایجاد کنیم. برای این منظور باید کاربران را به سه گروه سنی تقسیم کنیم : نوجوانان / جوانان و افراد میانسال به بالا. دسته اول سنی کمتر از ۱۶، دسته دوم سنی بین ۱۶ تا ۳۰ و دسته سوم، بالاتر از ۳۰ سال خواهند داشت. دستوری بنویسید که تعداد هر گروه را برگرداند.
- تعداد کاربران هراستان را به تفکیک لازم داریم. چگونه این اطلاعات را تولید میکنید ؟
- برای تمام افرادی که آفست timezone آنها برابر +۵:۰۰ است، فیلد شماره موبایل(cell) را کلا حذف کنید. حال تعداد افرادی را بیابید که شماره موبایل ندارند.
- میانگین سن کاربران استان تهران را با میانگین سن کاربران سایر استانها مقایسه کنید.
- کدام شهر بیشترین کاربر و کدام شهر، کمترین کاربر را دارد.
خروجی گام سوم
دستورات نوشته شده ، خروجی و زمان اجرای هر کوئری
گام چهارم – بررسی کارآیی شاخصها
با توجه به ساختار انعطاف پذیر مانگو، استفاده از شاخصها درفیلدهایی که درجستجوها، به کرات استفاده می شوند نقش مهمی در کارآیی برنامه ما خواهد داشت. برای برخی از سوالات فوق که در زیر تعیین شده است، ابتدا مشخص کنید چه شاخصی باید ایجاد شود (روی چه فیلدهایی/صعودی یا نزولی) و بعد از ایجاد شاخص، میزان افزایش سرعت پاسخگویی به همان سوال را گزارش کنید.
سوالات :
گام دوم : سوال ۱و ۴ و ۵
گام سوم : سوال ۲ و ۵.
گام پنجم – ( اختیاری – نمره اضافی)
با استفاده از امکان Map/Reduce[5] در مانگودیبی، میانگین سن کاربران را به تفکیک هر شهر به دست آورید.
پاسخ به سوالات
- بعد از نصب مانگودیبی، بهجای خط فرمان مانگودیبی از ابزار گرافیکی بسیار قدرتمند Studio 3T استفاده میکنیم که نسخهی قدرتمندتر
Robo 3T
میباشد. - در Studio 3T، ابتدا برای اتصال به DB Server به آدرس
localhost:27017
متصل میشویم. سپس دیتابیس metadata را بهصورت زیر ایجاد میکنیم:
پس از ایجاد دیتابیس metadata
، کالکشن users
را به دیتابیس metadata
اضافه مینماییم:
گام اول تمرین – درج دادهها
ابتدا گزینهی
را انتخاب کرده تا به خط فرمان مانگودیبی دسترسی داشته باشیم. سپس آدرس https://randomuser.me/api/?nat=ir را فراخوانی کرده، فایل JSON آنرا کپی میکنیم و سپس اطلاعات موجود در فیلد results را با دستور زیر در کالکشن users وارد میکنیم:
همین عمل را سه بار تکرار کرده و در نهایت از کالکشن users خروجی میگیریم (در صفحهی بعد). همانطور که مشاهده میشود، به ازای هر insertای که داشتهایم، یک ObjectId
با نام _id بهعنوان کلید به مشخصات کاربر اضافه میشود.
سپس اطلاعات قبل را پاک کرده و با استفاده از کتابخانهی pymongo و بهصورت زیر، اطلاعات ۱۰۰۰۰۰ کاربر تصادفی را در مانگو ذخیره میکنیم:
در نهایت، با دستور count مطمئن میشویم که ۱۰۰۰۰۰ کاربر تصادفی در کالکشن users ذخیره شده باشد:
گام دوم تمرین – دستورات اصلی
- نام و نامخانوادگی کاربرانی که بالای ۵۰ سال سن داشته و ساکن نیشابور باشند:
کوئری:
خروجی:
* لیست کامل خروجی شامل ۹۹۵ مورد است.
زمان اجرا:
- نام خانوادگی، آدرس و شماره موبایل کاربرانی که بیش از ۲۰ سال است که در سایت ما ثبتنام کردهاند:
نکته: با توجه به اینکه کاربران این سایت، حداکثر ۱۸ سال پیش در این سایت ثبتنامکردهاند، در نتیجه برای اینکه سوال ذکر شده، خروجی داشته باشد، بهجای ۲۰، از ۱۸ استفاده خواهیم کرد.
خروجی (تنها یک سند نمایش داده شده است ) :
زمان اجرا:
- افزودن فیلد year_persian به فیلد dob و registered:
کوئری:
توضیح: ابتدا نوع فیلد dob.date
که بهصورت رشته ذخیره شده است را به Date تغییر میدهیم. سپس با توجه به اینکه اختلاف تاریخ شمسی و میلادی ۲۲۶۸۹۹ روز میباشد، ۲۲۶۸۹۹ روز از این تاریخ کم میکنیم تا به تاریخ شمسی دست یابیم. در نهایت با استفاده از $year، سال را از تاریخ شمسی حاصل استخراج میکنیم و آنرا به فیلد جدیدی به نام dob.year_persian
اختصاص میدهیم. ما این عمل را برای registered.date
نیز انجام میدهیم.
خروجی کالکشن users بهصورت Tree:
* بههمین صورت تمام ردیفهای کالکشن users ویرایششده، حاوی زیرفیلد year_persian در فیلد dob و registered هستند. با توجه به اینکه خروجی این سوال، حدود ۱۸۰مگابایت حجم دارد، از ارائهی آن صرف نظر میکنیم.
زمان اجرا:
- دستوری بنویسید که با اجرا شدن آن در هر روز، نام و نام خانوادگی و ایمیل افرادی که در آن روز تولدشان است، به ما برگردانده شود:
کوئری:
توضیح: در کوئری بالا، نام و نامخانوادگی افرادی که روز و ماه تولدشان با روز و ماه جاری برابر است، برگردانده میشود. لازم به ذکر است که میتوانستیم بجای $month و $dayOfMonth از $dayOfYear استفاده کنیم، ولی چونکه با این روش، سال کبیسه در نظر گرفته نمیشود، بههمین دلیل از روش اول استفاده نمودیم.
خروجی:
زمان اجرا:
- ذخیره پسورد به شکل خام در اطلاعات کاربر، کاری غیرحرفهای است. میخواهیم این مشکل را برطرف کنیم. چه راه حلی برای حل مساله پیشنهاد میکنید؟
برای حل این مسئله، میتوان بجای پسورد از hex_md5 آن استفاده کرد.
راه حل را ابتدا روی یک سند خاص، امتحان کنید و مطمئن شوید بعد از اعمال تغییرات، آن کاربر خاص را با داشتن یوزنیم و پسورد، میتوانید پیدا کنید. سپس تمام کاربران را به روز رسانی کنید.
باتوجه به اینکه hex_md5 یک تابع جاوا اسکریپتی است، در نتیجه میتوان از این تابع در خط فرمان مانگودیبی استفاده کرد. ولی بایستی ورودی این تابع، پسورد کاربر مورد نظر باشد. ولی مشکلی که وجود دارد این است که در یک تابع جاوا اسکریپتی، نمیتوان با “$login.password”، مقدار پسورد کاربر مورد نظر را دریافت و از آن بهعنوان ورودی تابع hex_md5 استفاده کرد. بههمین دلیل ما مجبور شدیم که از foreach بر روی find استفاده کنیم تا بتوانیم با دستورات جاوا اسکریپتی، پسورد کاربر(های) مورد نیاز را استخراج و از آنها بهعنوان ورودی تابع hex_md5 استفاده کنیم. لازم به ذکر است که با توجه به اینکه استفاده از دستورات و توابع جاوااسکریپتی در مانگودیبی مجاز است، در نتیجه تمام دستورات زیر در خط فرمان مانگودیبی قابل اجرا است.
ابتدا با استفاده از کوئری زیر، بهجای پسورد کاربری با نام کاربری smallrabbit981، مقدار hex_md5 آنرا قرار میدهیم:
پسورد خام کاربر smallrabbit981 ، daytona بود. حال اگر بخواهیم که این کاربر را با داشتن نامکاربری و پسورد خام پیدا کنیم، کافیست از کوئری زیر استفاده کنیم:
که خروجی آن بهصورت زیر میباشد:
با توجه به اینکه مطمئن شدیم که عملکرد روش ما درست است، در نتیجه با کوئری زیر، پسورد تمام کاربران را بهروز رسانی میکنیم (البته قبل از آن، پسورد کاربر بالا را بهصورت قبل برمیگردانیم):
همانطور که از خروجی username و password چند کاربر اول کالکشن users بهصورت زیر مشخص است، پسورد کاربران تغییر کرده است:
زمان اجرای کوئری بهروز رسانی پسورد تمام کاربران نیز ۲دقیقه و ۲ ثانیه و ۵۰ صدم ثانیه بهطول انجامید.
گام سوم تمرین – دستورات تجمعی و آماری
- ابتدا بایستی کاربران را به سه گروه سنی کمتر از ۱۶ (نوجوان) / بین ۱۶ تا ۳۰ (جوان) / بالاتر از ۳۰ (میانسال به بالا) تقسیم کنید، سپس دستوری بنویسید که تعداد هر گروه را به ما برگرداند:
کوئری:
خروجی:
زمان اجرا:
- تعداد کاربران هر استان را به تفکیک تولید کنید:
کوئری:
خروجی:
زمان اجرا:
- فیلد شماره موبایل افرادی که آفست timezone آنها برابر +۵:۰۰ را حذف نمایید، سپس تعداد افرادی را بیابید که شماره موبایل ندارند.
کوئری حذف شماره موبایل:
کوئری جستوجوی تعداد افراد:
خروجی:
زمان اجرا:
- میانگین سن کاربران استان تهران را با میانگین سن کاربران سایر استانها مقایسه کنید:
کوئری:
درک اینجانب از سوال، این است که میانگین سن کاربران استان تهران را با میانگین سن دیگر کاربران مقایسه کنیم. بههمین دلیل، کوئری ما بهصورت زیر میباشد:
خروجی:
همانطورکه مشاهده میشود، میانگین سن کاربران استان تهران از میانگین سن دیگر کاربران کمتر است.
زمان اجرا:
کوئری: ولی اگر هدف سوال از مقایسه این است که مشخص کنیم که چه استانهایی، میانگین سن کمتر، چه استانهایی، میانگین سن برابر و چه استانهایی، میانگین سن بیشتر از استان تهران را دارند، کوئری آن بهصورت زیر میباشد:
خروجی:
همانطورکه مشاهده میشود، میانگین سن کاربران استان تهران از میانگین سن کاربران تمام استانهای دیگر کمتر است.
زمان اجرا:
- کدام شهر، بیشترین کاربر و کدام شهر، کمترین کاربر را دارد؟
کوئری:
در اینجا، ابتدا تعداد کاربران هر استان را محاسبه کرده، سپس یک بار، استانها را بر اساس تعداد کاربرانشان بهصورت نزولی مرتب کرده و اولین استان را برمیداریم، و یک بار، استانها را بر اساس تعداد کاربرانشان بهصورت صعودی مرتب کرده و اولین استان را برمیداریم. سپس این دو استان را بهصورت مناسب در خروجی نمایش میدهیم.
خروجی:
زمان اجرا:
گام چهارم تمرین – بررسی کارآیی شاخصها
به منظور ایجاد شاخص، مراحل زیر در ابزار Studio 3T طی میشود:
ابتدا بر روی کالکشن users کلیک راست نموده، سپس گزینهی Add Index را انتخاب میکنیم.
حال از طریق گزینهی Add Field(s)، فیلد(های) مورد نظر را انتخاب مینماییم.
پس از انتخاب فیلد(های) مورد نظر، Index Type آنها را از بین یکی از موارد زیر انتخاب میکنیم:
استفاده از شاخص در گام دوم – سوال ۱ :
استفاده از شاخص در گام دوم – سوال ۴:
استفاده از شاخص در گام دوم – سوال ۵:
استفاده از شاخص در گام سوم – سوال ۲ :
استفاده از شاخص در گام سوم – سوال ۵ :
همانطور که از جداول بالا مشخص شد، استفاده از شاخص در مسئلهی گام دوم – سوال ۱ در کاهش زمان اجرا بسیار تاثیرگذار بوده است (که دلیل آن این است که فقط از find استفاده شده است). ولی استفاده از شاخص در سایر موارد، تاثیر چندانی در کاهش زمان اجرا نداشته است، که احتمالا دلیل آن به نوع کوئری نوشته شده برمیگیرد.
گام پنجم تمرین – Map Reduce
اسکریپت زیر، قابلیت بازیابی میانگین سن کاربران به تفکیک هر شهر را دارد. این اسکریپت با توجه به اینکه به زبان جاوا اسکریپت نوشته شده است، در نتیجه میتواند در خط فرمان مانگودیبی نیز اجرا شود:
توضیح: خروجی تابع map، لیستی از زوج مرتبهای (مقدار, کلید) بهصورت (سن, شهر) است. سپس تابع reduce، به ازای هر شهر، میانگین سن کاربران آنها را محاسبه و برمیگرداند.
با اجرای این اسکرپیت در خط فرمان مانگودیبی، خروجی آن بهصورت زیر خواهد بود(بخش ابتدایی خروجی ):
زمان اجرا:
سخن پایانی
راه حل ارائه شده فوق برای سوالات داده شده ، تنها یک روش از بین چندین روش انجام پرس و جوها در مانگودیبی است. برای مشاهده سایر راه حل ها و ایده گرفتن از آنها میتوانید از این راهحل ها هم استفاده کنید.
[۱] https://bit.ly/2XWSqM7
[۲] https://robomongo.org/
[۳] https://openmymind.net/mongodb.pdf
[۴] https://pymongo.readthedocs.io/en/stable/tutorial.html
[۵] https://api.mongodb.com/python/2.0/examples/map_reduce.html
با سلام
با تشکر از این مطالب خوب
امکان انتشار بخش دوم و سوم هم وجود داره؟
مطالب آماده است اما زمان کافی برای ویرایش نهایی و انتشار آنها ندارم. سعی می کنم به تدریج بقیه قسمتهای این تمرین شامل کار با دیتابیس neo4j و hbase را هم منتشر کنم. ممنونم از توجه و پیگیری شما.