نکته‌ای جالب از indexها و Time-To-Live Index

Interesting Points of Indexes and Time-To-Live Index

24 اردیبهشت 1401
درسنامه درس 54 از سری دوره جامع آموزش MongoDB
MongoDB: نکته ای جالب از index ها و Time-To-Live Index (قسمت 56)

قبل از شروع این جلسه باید مثالی خاصی از جلسه قبل (partial index ها) را بررسی کنیم. زمانی که partial index ها را با index های unique ترکیب می کنیم به نتیجه جالبی می رسیم اما بهتر است این کار را به صورت عملی انجام بدهیم. برای انجام این کار باید یک کالکشن جدید به نام users تعریف کنیم و داده های جدیدی را در آن قرار بدهیم:

db.users.insertMany([{name: "Amir", email: "Amir@test.com"}, {name: "Nastaran"}])

در اینجا دو کاربر جدید را وارد کالکشنی جدید به نام users کرده ایم که در آن کاربر اول ایمیل دارد اما کاربر دوم فقط name را دارد و از ایمیل خبری نیست. در مرحله بعدی باید یک index را برای این کالکشن تعریف کنیم. من در ابتدا کد زیر را تست می کنم:

db.users.createIndex({email: 1})

این کد با موفقیت اجرا می شود بنابراین می دانیم که مشکلی نیست. حالا این ایندکس را حذف کنید تا این بار آن را به صورت unique تعریف کنیم:

db.users.dropIndex({email: 1})

با این کار ایندکس ما حذف می شود و می توانیم یک index جدید به صورت unique تعریف کنیم:

db.users.createIndex({email: 1}, {unique: true})

این کوئری نتیجه زیر را به ما می دهد:

"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,                  
"numIndexesAfter" : 2,                   
"ok" : 1                                 

یعنی همه چیز به خوبی اجرا شده است و مشکلی نداریم. از اینجا به بعد فیلد email دارای ایندکس یکتایی است که اجازه نمی دهد مقادیر تکراری اضافه شوند. در مرحله بعد باید یک کاربر را به این کالکشن اضافه کنیم:

db.users.insertOne({name: "Pooya"})

این کد کاربر جدیدی را اضافه می کند که email ندارد اما اگر آن را اجرا کنید به خطای عجیبی برمی خورید:

"errmsg" : "E11000 duplicate key error collection: contactData.users index: email_1 dup key: { email: null }"

همانطور که می بینید خطا می گوید ایمیل وارد شده تکراری است! از آنجایی که کاربر Nastaran ایمیلی نداشته است، مقدار آن برایش null در نظر گرفته می شود و حالا که می خواهیم Pooya را به آن اضافه کنیم به ما خطا داده می شود چرا که Pooya نیز بدون ایمیل است بنابراین null تکرار خواهد شد!

بنابراین به یاد داشته باشید که MongoDB رفتار خاصی دارد. اگر داده های ایندکس شده شما مقداری نداشته باشند، برای MongoDB به صورت null در نظر گرفته می شوند. اگر می خواهید این رفتار را نداشته باشید، باید ایندکس ها را به شکل دیگری تعریف کنید. من در ابتدا ایندکس قبلی را حذف می کنم:

db.users.dropIndex({email: 1})

سپس با استفاده از partial index ها برای کاربرانی که ایمیل ندارند، index نمی زنیم:

db.users.createIndex({email: 1}, {unique: true, partialFilterExpression: {email: {$exists: true}}})

در اینجا گفته ام فقط برای email هایی index بزن که وجود داشته باشند (اپراتور exists$). به همین راحتی مشکل را حل کرده ایم و حالا می توانیم Pooya را اضافه کنیم:

db.users.insertOne({name: "Pooya"})

آشنایی با ایندکس های TTL یا Time-To-Live

آخرین نوع ایندکسی که باید در این جلسه بررسی شود ایندکس های TTL هستند. این نوع از ایندکس ها برای داده هایی هستند که به اصطلاح self-destroying (خودنابودگر) تلقی می شوند، یعنی داده هایی مانند session ها که پس از مدت مشخصی از بین می روند. بیایید به صورت عملی با آن ها آشنا شویم. من برای شروع یک کالکشن جدید به نام sessions می سازم:

db.sessions.insertOne({data: "asdfgaiphvasd", createdAt: new Date()})

اولین سند ما دو خصوصیت data و createdAt دارد. data یک رشته تصادفی و بی معنی است و createdAt از تابع Date استفاده کرده است تا تاریخ فعلی را به عنوان مقدار خودش قرار دهد. برای چک کردن آن می گوییم:

db.sessions.find().pretty()

نتیجه به شکل زیر خواهد بود:

"_id" : ObjectId("5ebba04e38d7942a204d1b73"),    
"data" : "asdfgaiphvasd",                        
"createdAt" : ISODate("2020-05-13T07:22:54.140Z")

نوع داده ISODate برای فرمت زمان در MongoDB تعریف شده است و دقیقا تاریخ فعلی را دارد (در زمان نوشتن این مقاله). ممکن است ساعت آن برایتان اشتباه باشد که آن بستگی به سرور شما دارد و فعلا به آن کاری نداریم. بنابراین این می شود یک session ساده برای ما. حالا باید برای این داده ها یک index از نوع TTL تعریف کنیم (البته می توانید ایندکس عادی نیز تعریف کنید ولی هدف این جلسه TTL ها هستند):

db.sessions.createIndex({createdAt: 1}, {expireAfterSeconds: 10})

خصوصیت expiresAfterSeconds (به معنی «پس از فلان ثانیه منقضی می شود») یکی از خصوصیات ویژه MongoDB است که فقط در فیلد های Date (نوع داده ISODate) کار می کند. اگر آن را برای فیلد های عادی تعریف کنید، MongoDB دستور شما را نادیده می گیرد. در کوئری بالا گفته ایم که پس از 10 ثانیه، فیلد های ما باید حذف شوند. به نظر شما اگر پس از 10 ثانیه، دستور زیر را اجرا کنید چه اتفاقی می افتد؟

db.sessions.find().pretty()

هیچ اتفاقی نمی افتد! چرا که تنها داده ما در کالکشن، قبل از اضافه شدن ایندکس، اضافه شده بود و ایندکس ها نمی توانند داده های قبل از خود را پاک کنند. بنابراین برای تست کردن آن باید یک داده جدید را وارد کالکشن کنیم:

db.sessions.insertOne({data: "gasjtpsdpassss", createdAt: new Date()})

حالا اگر find را اجرا کنیم، هر دو سند را می بینیم اما اگر پس از 10 ثانیه دوباره find را اجرا کنیم، هیچ نتیجه ای را نمی گیریم! یعنی پس از 10 ثانیه، هر دو سند (حتی سندی که قبل از ایندکس اضافه شده بود) به طور کامل پاک شده اند. چرا؟ به دلیل اینکه اضافه کردن یک سند جدید، MongoDB را وادار می کند تا کل کالکشن را ارزیابی کند (مثلا ببیند آیا ایندکسی وجود دارد که لیست آن را به روز رسانی کند و کار های دیگر) و این ارزیابی دوباره باعث حذف شدن داده های قبلی شده است.

این قابلیت، بسیار کاربردی است! چرا که به ما اجازه می دهد بدون شلوغ کردن پایگاه داده، داده های موقتی مانند session ها را ذخیره کنیم و خود پایگاه داده آن ها را پاک خواهد کرد.

نکته: شما اجازه استفاده از TTL ها روی compound index ها را ندارید. ایندکس های TTL فقط می توانند در single field index ها (ایندکس های تکی که ترکیبی نباشند) استفاده شوند، آن هم به شرطی که از نوع ISODate باشند.

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

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

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