حتما همه تون تا حالا عنوان Nosql به گوشتون خورده. موضوعی که جدید نیست ولی این روزها تبش داغ تره. خیلی از برنامه نویسانِ گران قدر صرفا به علت دهن پرکن یا باحال بودن و بدون توجه به کاربرد، امکانات یا موقعیت ازش استفاده می کنن و سرشون هم بالا هست. اگر با Nosql آشنا نیستید این ارائه آقای Martin Fowler می تونه معرفی خوبی باشه.
من برای اینکه برم سمت nosql دو دلیل داشتم:
قبل از اینکه دلایلم رو توضیح بدم باید به یک نکته اشاره کنم. وقتی به صورت کلی پیرامون دیتابیس صحبت می کنیم موضوعاتی مثل transaction یا ACID یا scaling خیلی مهم و قابل بحث هستند ولی این رو در نظر بگیرید تقریبا هیچ کدوم در زمینه دیتابیس اندروید موضوعیت ندارند. ما معمولا سمت دیتابیس اندروید کار critical ای رو انجام نمیدیم یا حجم دیتاها از حدی بیشتر نمیشه چرا که در این صورت با توجه به گستردگی دیوایس ها، اپلیکیشن ممکنه به صورت جدی با مشکل پرفورمنس روبرو بشه. پس این موضوعات در حوزه دیتابیس مهم، و حتی خیلی مهم، هستند ولی به شکل به خصوص در دیتابیس اندروید خیر. بیشتر کاربرد دیتابیس در اندروید، نگه داشتن دیتا برای ارائه آفلاین به یوزر و پرفورمنس بهتر هست.
تیم بک اند شرکت ما از دیتابیس Nosql استفاده می کنه. طبیعتا خروجی کار اون ها، که در قالب api به دست ما میرسه، با همین مایندست هست. هماهنگ کردن همچین خروجی ای با sql هزینه خواهد داشت. فارغ از هزینه هر تغییر کوچکی که در مدل ایجاد میشه ممکنه سبب تغییر ساختاری سمت اندروید بشه. همچنین با توجه به اینکه سمت اندروید کوئری هایی که من روی دیتابیس می زنم پیچیدگی خاصی نداره عملا پرداخت این هزینه بی فایده هست. با در نظر گرفتن ساختار دیتایی که بک اند در اختیار ما قرار میده و همینطور موقعیت های سمت اندروید، دیتابیس Nosql سازگاری خیلی بیشتری با نیازهای ما داره.
همینطور که اشاره کردم ما کوئری های خیلی پیچیده ای نداریم ولی از اون طرف تعداد عملیات write و مخصوصا read زیادی داریم. یکی از فیچر های اپلیکیشن ما chat هست به همین دلیل حجم زیادی پیام در دیتابیس ذخیره و خوانده میشه. نتیجتا سرعت برای ما عنصر مهمی هست. اینکه Nosql از sql سریع تره گزاره درستی نیست چون خیلی به شرایط، ساختار داده و ... بستگی داره. اگر شما کوئری های سنگینی دارید احتمالا sql خیلی بهینه تر از Nosql عمل می کنه. ولی در شرایط ما Nosql گزینه ی بهینه تر هست.
با یک سرچ ساده متوجه میشید که تعداد زیادی گزینه برای Nosql سمت اندروید وجود داره ولی تعداد گزینه هایی که بشه بهشون اعتماد کرد زیاد نیست. من در نهایت به دو گزینه رسیدم:
برای انتخاب گزینه مناسب تر یک سری ملاک تعریف کردم:
اولین ادعایی که Object box مطرح می کنه که در نگاه اول دل آدم رو می بره سرعتشه. اون ها ادعا می کنن ۱۰ برابر سریع تر از گزینه های sql ای هستن. برای اینکه ادعاشون رو ثابت کنن یه پروژه open source دارن که اومدن performance خودشون، realm و sqlite رو مقایسه کردن. پروژه رو می تونید اینجا ببینید.
من اپشون رو نصب کردم و واقعا سرعت متفاوتی داره. خیلی متفاوت. در همه زمینه ها. انقدر سرعت بالایی دارن که نیازی به استفاده async ازش وجود نداره! شما می تونید تو main thread باهاش کار کنید! البته اگر بخواید می تونید با استفاده از Rx ازش به شکل async هم استفاده کنید ولی تو داکیومنتش مثال هایی که داره تقریبا همگی sync هستند.
یک نکته مثبت دیگه راجع به object box سیستم query ایش بود. به نظر میاد میشه کوئری ها با سطح معقولی از پیچیدگی رو به راحتی پیاده کرد و خوبیش این بود که compile time چک هم داشت.
اما همه چیز راجع به object box انقدر مثبت نبود که ای کاش می بود ):
مشکل اول: داکیومنتشون لزوما آپدیت نیست. مثلا توی این ایشو یه فیچر مهم اضافه کردن ولی من نتونستم تو داکیومنتشون پیداش کنم.
مشکل دوم: بررسی فیچرهای مهمی که تعداد قابل توجهی بهشون نیاز دارن با فاصله زمانی خیلی طولانی انجام شده. مثلا این ایشو که اتفاقا خیلی هم مهم هست و در ادامه راجع بهش صحبت می کنم سال ۲۰۱۷ مطرح شده ولی هنوز اضافه نشده. ۴ سال! با بررسی گیت هاب به نمونه های مشابه برخورد می کنید.
مشکل سوم: جنس آیدی باید فقط long باشه. برای من که جنس آیدی هام string هست خیلی این قضیه می تونه مهم باشه و کار رو سخت بکنه. اینطور در عمل نمی تونم از یه حجم خوبی از امکاناتش استفاده کنم چون آیدی لوکال و سرور با هم فرق می کنن. از طرفی اگر بخوام یه hash بنویسم که مثلا آیدی های string رو به long تبدیل کنه هزینه و مشکل خودش رو داره.
مشکل چهارم: برای استفاده از object box باید مدلم رو تغییر میدادم. مثلا برای تعریف روابط One-to-Many یا Many-to-Many باید از کلاس های generic خودش استفاده می کردم که خیلی مورد نظرم نبود. ترجیح میدادم بشه این کار رو با annotation جلو برد. البته این موضوع رو احتمالا میشد با دیزاین پترن DTO برطرف کرد ولی در کل نیاز به تغییر زیادی داشت.
مشکل پنجم: من وقتی داشتم object box رو به بخشی از پروژه اضافه می کردم به مشکلی برخوردم اما تقریبا نتیجه ی تمام سرچ ها به document اشون می رسید. این قضیه کمی نگران کننده بود چون به نظر می اومد community خوبی نداره و ممکنه روزی دست آدم رو بذاره تو پوست گردو.
پی نوشت: سید محمد حسین جعفری در یکی از رویداد های لاگ کت ارائه خوبی داره با عنوان Android Offline که به object box هم اشاره می کنه. شاید برای آشنایی اولیه دیدن این ارائه هم مفید باشه.
راجع به پرفورمنس realm افسانه های زیادی وجود داره :) مثلا تو این مقاله یه bench mark از چند تا دیتابیس و ORM آورده شده. با توجه به این نتایج realm پرفورمنس خوبی داره و حتی در read با اختلاف گزینه برتر هست ولی می تونید برید کامنت هاش رو بخونید. برو بچه های object box اومدن گفتن که این دروغی بیش نیست و ما از همه خفن تریم :)
یا مثلا تو این مقاله سازنده ORMLite حسابی توپیده بهشون و گفته bench mark هاشون درست نیست. بنده خدا از خودش هم حسابی مایه گذاشته.
گفته غیر ممکنه که سرعت insert توی ORMLite (که خودش نوشته) از greenDAO انقدر بیشتر باشه :) به این مورد به عنوان نمونه ای از درست نبودن دیتا اشاره کرده.
یا مثلا تو این مقایسه در عمل realm در هیچ حوزه ای در برابر حتی گزینه های sql ای حرفی برای گفتن نداره.
آدم نمی دونه حرف کدوم رو باور کنه. من از همون پروژه object box استفاده کردم. برنامه رو روی گوشی خودم تست کردم و میشه گفت realm تقریبا در اکثر پرامترها نسبت به room و greenDAO برتری داشت غیر از یک مورد مهم که در ادامه بهش می پردازم.
من با فرض اینکه realm از لحاظ پرفورمنس حداقل از room بدتر نیست به بررسی بیشترش پرداختم. اولین نکته ای که نظر رو جلب می کنه اینه که توسط mongoDB توسعه داده شده که این خیلی می تونه نکته مهمی باشه چون که mongoDB یکی از گزینه های خیلی معروف و پرکاربرد حوزه Nosql هست و حتما این تیم با همه نیازهای یه دیتابیس Nosql آشنا هست.
اما مشکلاتی وجود داشت که باعث شد من خیلی جلوتر نرم.
مشکل اول: realm نسبت به همه رقباش حجم دیتابیسش خیلی بیشتره. قطعا این موردی هست که خیلی برای کاربر نمی تونه جذاب باشه.
مشکل دوم: ظاهرا realm نسبت به بقیه مصرف ram بیشتری داره. با توجه به اینکه ما گوشی های زیادی رو ساپورت می کنیم که ممکنه ram بالایی هم نداشته باشن این مورد هم خیلی تو ذوق زد.
مشکل سوم: با اینکه توی تستی که من انجام دادم سرعت realm از room بیشتر بود ولی یک مشکل خیلی بزرگ داشت. access time اش نسبت به همه خیلی بیشتر بود و با توجه به اینکه من تعداد دسترسیم به دیتابیس زیاده این خیلی می تونه تاثیر گذار باشه. یعنی اگر من ۱۰۰۰ تا entity رو بخوام یکجا وارد دیتابیس کنم احتمالا realm نسبت به room بهتر عمل می کنه ولی وقتی بخوام ۱۰۰۰ تا entity رو مجزا وارد کنم access time خودش رو می تونه نشون بده.
مشکل چهارم: realm حدود ۲ تا ۳ مگابایت حجم اپلیکیشن رو بالا می بره که رقم خیلی قابل توجهی هست. خودشون پیشنهاد دادن که برای برطرف کردن این مشکل می تونید برای معماری های مختلف خروجی مجزا بگیرید ولی اون خودش یه پیچیدگی جدید اضافه می کنه.
مشکل پنجم: وظیفه بستن کانکشن دیتابیس با من بود. این یک مقدار می تونه تریکی باشه مخصوصا برای اپ ما که دسترسی به دیتابیس زیاد و از جاهای مختلفی هست که گاها ربطی به ui هم ندارن و زمان بسته بودن برنامه اتفاق می افتن. اینکه بستن کانکشن دست ما باشه می تونه احتمال memory leak رو بالاتر ببره.
در نهایت هیچ کدوم از این دو گزینه نتونستن نظر من رو جلب کنن(یا شاید بهتر بگم با نیازهای من سازگار نبودن). اگر اپلیکیشن تون با دیتابیس زیاد کار می کنه، مشکلی با اینکه جنس آیدی long باشه ندارید، می خواید از Nosql استفاده کنید و در اول راه هستید فکر می کنم object box می تونه گزینه مناسبی باشه. سرعتش به شکل چشم گیری بیشتره و امکانات قابل قبولی هم داره. من هیچ برتری در realm ندیدم نسبت به بقیه. فقط اینکه امکان encryption داره که فکر نمی کنم خیلی به کار بیاد ولی اگر برای شما مهمه حتما یه نگاه بهش بندازید.