Skip to content

Instantly share code, notes, and snippets.

@mjebrahimi
Last active December 12, 2020 18:23
Show Gist options
  • Save mjebrahimi/24ca1a976477faaa60baa681d4d0d92b to your computer and use it in GitHub Desktop.
Save mjebrahimi/24ca1a976477faaa60baa681d4d0d92b to your computer and use it in GitHub Desktop.
چالش های متداول طراحی مدل ها در دیتابیس Mongo

چالش های متداول طراحی مدل ها در دیتابیس Mongo

یکی از مهمترین مزایای دیتابیس های nosql (مخصوصا داکیومنت بیس ها مانند مونگو) امکان embed کردن مقادیر مرتبط داخل یک داکیومنت هست که باعث میشه بتونیم عملیات جوین (که بسیار هزینه بر هست) رو حذف کنیم که موجب افزایش پرفرمنس عملیات واکشی میشه

مشکلی که اکثر برنامه نویس های sql کار به هنگام مهاجرت به mongo با اون مواجه میشن اینه که همچنان میخوان با دید sql ای به قضیه نگاه کنن.

اگر قرار باشه با دید sql با mongo برخورد کنیم همون بهتر که از یه دیتابیس sql ایی مانند sql server استفاده کنیم که هم قابلیت relation داره، هم با ef سازگار هست و هم بدلیل وجود امکان navigation property ها، امکان کوئری نویسی ساده تر رو داره و نیاز به جوین نویسی های پیچیده نداره

مهم ترین نکته به هنگام طراحی موجودیت ها در mongo اینه که درک درستی از مدل سازی و مدیریت روابط با دید nosql داشته باشیم

توی mongo همچنان امکان جوین نویسی های (حتی پیچیده) نیز توسط دستور aggregate و lookup وجود داره ولی تا جایی که میشه باید موجودیت ها و روابط جوری مدل سازی بشن که حتی الامکان نیازی به اون ها نباشه

دیتابیس mongo روش های مختلفی رو برای روابط (1-1 و 1-N و N-N) پشتیبانی میکنه از جمله:

  1. روش Embedded pattern
  2. روش Reference pattern (که Linking هم بهش میگن)
  3. روش Subset pattern
  4. روش Bucketing pattern

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

https://docs.mongodb.com/manual/applications/data-models-relationships/ http://learnmongodbthehardway.com/schema/schemabasics/

روش Embedded pattern

ایده این روش اینه که یک طرف رو در طرف دیگه embed کنیم. مثلا در مثال book و author، ابجکت های author رو داخل book ذخیره میشه

مزایا:

عدم نیاز به جوین نویسی و در نتیجه ساده شدن کوئری ها کاهش تعداد read operations و افزایش perfromance

معایب:

ممکنه باعث افزایش حجم داکیومنت بشه مخصوصا در حالت one-to-many (حداکثر حجم داکیومنت توی mongo برابر با 16 مگابایت هست) تکرار شدن دیتا (duplication) و درنتیجه زحمت بیشتر برای اعمال consistency

وقتی باید استفاده بشه که:

دیتای embed شده کم هست (یعنی به حداکثر 16MB نمیرسه) و یا معمولا به همراه طرف دیگه واکشی میشه (مثلا در مثال book و author، معمولا کتابها به همراه نویسنده ها واکشی میشن)

روش Reference pattern

این روش مانند روش معمول sql ایی هست که در اون Id یک طرف در طرف دیگه نگه داری میشه. مثلا در مثال book و author، آیدی نویسنده رو سمت کتاب ذخیره میکنیم

مزایا :

جلوگیری از تکرار شدن دیتا (duplication) و حذف زحمت consistency رخ ندادن مشکل maximum size داکیومنت ها

معایب:

نیاز به join زدن و پیچیده شدن کوئری ها افزایش تعداد read operations و کاهش perfromance

وقتی باید استفاده بشه که:

وقتی روش های قبلی جواب نباشه (این روش خیلی توصیه نمیشه چرا که طبق صحبت اولیه، مزیت اصلی mongo رو از بین میبره)


بحث مدل سازی موجودیت ها و روابط در مونگو اگه بخوایم عمیق بشیم بحث پیچیده ای هست. مثلا بر خلاف sql که در اون ایدی parent رو در سمت child نگه میداریم (یعنی هر فرزند میدونه پدرش کیه)، در mongo این امکان وجود داره که آیدی child ها رو سمت parent نگه داریم (یعنی هر پدر بدونه بچه هاش کیا هستند)

یا مثلا میشه از روش embedded و referenced به صورت ترکیبی استفاده کرد. مثلا طرف اول در طرف دوم embed بشه و آیدی طرف دوم هم در طرف اول به صورت reference ذخیره بشه

به همین منوال امکان استفاده از embedded pattern دو طرفه و یا reference pattern دو طرفه هم وجود داره که مثلا در اون آیدی هر دو طرف، در طرف دیگه ذخیره میشند

مورد دیگه اینکه در حالت embedded pattern کدوم طرف رو در کدوم طرف embed کنیم بهتره یا در حالت reference pattern، آیدی کدوم طرف رو در کدوم طرف ذخیره کنیم؟

همه این موارد مزایا-معایب و usecase های خودشون رو دارند و واقعا جاش نیس به صورت detail واردش بشم.

بگذریم...

تا اینجای کار گفتیم روش embed سریع تر هست ولی باعث duplication و در نتیجه زحمت بیشتر برای اعمال consistency میشه و روش reference کند تر هست ولی در عوض data duplication و inconsistency نداره

و اما نکته ای به هنگام استفاده از روش embed خیلی باید حواسمون بهش باشه اینه که در این روش به دلیل تکرار (duplicate) موجودیت ها، ممکنه inconsistency (عدم یکپارچگی اطلاعات) به وجود بیاد و این قضیه رو خودمون باید به صورت دستی هندل کنیم که کار نسبتا سختی هست

مثلا در یک فروشگاه فرض کنید موجودیت User برای خودش مستقل هست، از طرفی در موجودیت های سفارش و فاکتور (Order و Invoice) هم به صورت embed ذخیره شده (اگه اطلاعات کامل کاربر نه، حداقل نام و نام خانوادگیش به صورت embed ذخیره شده)

حالا یه کاربر میاد 10 تا سفارش ثبت میکنه و 1 فاکتور هم براش ثبت میشه. در این شرایط اگر اون کاربر بیاد و مشخصاتش (مثلا نام و نام خانوادگیش) رو ویرایش کنه، برنامه نویس باید حواسش باشه که بره توی 10 تا سفارش دیگه و 1 فاکتور مربوط به این کاربر هم اون ها رو آپدیت کنه

این یه مثال ساده بود؛ تو یه سیستم واقعی و بزرگ؛ شرایط خیلی پیچیده تر از این هست. اگر برنامه نویس نتونه این قضیه رو به درستی مدیریت کنه، مشکل عدم یکپارچگی اطلاعات به وجود میاد که برای خودش مصیبتی هست

نهایتا سلوشن ایده عالی که میتونم پیشنهاد بدم (اگه پروژه بزرگ هست) اینه که از CQRS استفاده بشه به این صورت که برای سمت write از یک دیتابیس SQL به همراه EF استفاده بشه و بعد از اعمال تغییرات، یک Event (رخداد تغییر اون موجودیت)، Publish بشه و یه سرویس دیگه (Subscriber) بیاد و از روی دیتایی که توی دیتابیس SQL ایی وجود داره، دیتای متناسب با mongo اش رو تولید و ذخیره کنه (که بهش Materialized View هم میگن)

این طوری هم موقع write کردن راحتیم. چون data duplication و inconsistency نداریم و هم از ابرازی مانند EF میتونیم استفاده کنیم که کارمون رو خیلی راحت تر میکنه

و هم موقع read کردن راحتیم. چون دیتاها به صورت embed شده هستند، نیاز به جوین و کوئری نویسی های پیچیده نداریم و پرفرمنس بالایی هم خواهیم داشت. ونیز ساختار دیتا هامون هم به بهینه ترین شکل ممکن برای read ذخیره شدند (تکنیک materialized view)

تنها مشکل این روش بحث Eventual Consistency هست به این معنی که دیتا های دیتابیس write ممکنه با تاخیر، سمت دیتابیس read بشینن و sync بشن که اون هم در صورت لزوم با استفاده از روش های Strong Consistency قابل حل هست

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment