MongoDB به Postgres
NOTE: این پست وبلاگ ادغامی از MongoDB به Postgres (2024-03-06) و Sequelize در برابر Prisma (2023-05-25) من است. وبلاگهای اصلی حذف شدهاند و این وبلاگ جای آنها را گرفته است، زیرا هر دو عملاً شامل همان محتوا/اطلاعات بودند. مهاجرت در اوایل مارس ۲۰۲۳ آغاز شد، سوئیچ در اواسط نوامبر ۲۰۲۳ رخ داد، و همه نمونههای سیستم قدیمی MongoDB بهطور کامل در اوایل ژانویه ۲۰۲۴ خاموش شدند.
مقدمه
در دوران کاریام در eBay، با آنچه به چالشبرانگیزترین مسئله فنیِ حرفهام تبدیل شد روبهرو شدم: مهاجرت سیستم مدیریت ذخیرهسازی (STMS) از MongoDB به Postgres. این فقط یک تعویض سادهٔ پایگاهداده نبود؛ بلکه یک تحول کامل معماری برای یک سیستم حیاتی بود که بیش از ۱.۵ میلیون متریک در دقیقه را در مراکز دادهٔ eBay دریافت میکند، با الزامِ حداقل زمان ازکارافتادگی و حفظ تقریباً همهٔ قابلیتهای موجود.
STMS چیست؟
سیستم مدیریت ذخیرهسازی (STMS) بهعنوان یک ابزار داخلی حیاتی برای تیم زیرساخت سرویس و ذخیرهسازی (SSI) eBay عمل میکند. این سیستم دستگاهها را در سراسر مراکز دادهٔ eBay پایش و مدیریت میکند و به مهندسان اجازه میدهد:
- متریکها را از دهها آرایه، سوئیچ، میزبان، گروه دیسک و کلاستر پایش کنند
- رسیدگی به هشدارها برای سوئیچها و آرایهها
- انجام کارهای پیشرفته مانند تخصیص میزبان
- دسترسی به دادههای بلادرنگ برای سایر سرویسهای داخلی eBay
STMS بیش از ۷۰ آرایه، ۶۰ سوئیچ، ۱۱۰۰ میزبان، ۹۰۰ گروه دیسک و ۲۰۰ کلاستر را در ۳ مرکز دادهٔ eBay پوشش میدهد. با توجه به نقش حیاتی آن در زیرساخت eBay، هرگونه ازکارافتادگی یا از دست رفتن قابلیتها مستقیماً بر سرویسهای اصلی و عملیات کسبوکار شرکت تأثیر میگذاشت.
چالش
چرا این مهاجرت ضروری بود
تصمیم برای مهاجرت از MongoDB به Postgres سبکسنگین گرفته نشد. اگرچه MongoDB در ابتدا بهخوبی به STMS خدمت کرده بود، پیچیدگی رو به رشد روابط دادههای ما و نیاز به قابلیتهای پیشرفتهتر در کوئریزنی، Postgres را به راهحل بهتر بلندمدت برای مورد استفادهٔ ما تبدیل کرد.
چه چیزی این مسئله را دشوار میکرد
پیچیدگی این مهاجرت از چند چالش بنیادی ناشی میشد:
۱. تفاوتهای بنیادی پایگاهداده MongoDB و Postgres ذاتاً پایگاهدادههای متفاوتی هستند. MongoDB یک پایگاهدادهٔ مبتنی بر سند (NoSQL) است، یعنی دادهها بهصورت JSON در مجموعهها ذخیره میشوند، مانند سندها در یک بایگانی. Postgres یک پایگاهدادهٔ رابطهای (SQL) است، یعنی دادهها بهصورت ردیف در جدولها ذخیره میشوند، مانند یک صفحهگسترده.
۲. معماری کدبیس کل بکاند STMS برای پردازش و مدیریت دادهها بهصورت JSON ساخته شده بود و از بستههایی استفاده میکرد که فقط با MongoDB برای عملیات پایگاهداده سازگار بودند. این یعنی فقط تغییر پایگاهداده مطرح نبود، بلکه باید نحوهٔ مدیریت داده در کل برنامهمان بازساخت میشد.
۳. الزام حداقل زمان ازکارافتادگی بهدلیل اینکه STMS بهعنوان یک ابزار داخلی چقدر حیاتی است، در طول مهاجرت هیچگونه ازکارافتادگیای نمیتوانست وجود داشته باشد. سیستم باید در تمام فرآیند، بیش از ۱.۵ میلیون متریک در دقیقه را سرویس میداد.
۴. بازهٔ زمانی فشرده و تجربهٔ محدود مهاجرت باید ظرف چند ماه تکمیل میشد، و در ابتدا برنامهٔ اجرایی روشنی وجود نداشت. نه من و نه همکارانم تجربهٔ مهاجرت یک کدبیس قدیمیِ بزرگ از پایگاهدادههای NoSQL به SQL را نداشتیم، و من نیز تجربهٔ قبلی محدودی با Postgres داشتم.
۵. مقیاس و پیچیدگی این مهاجرت شامل تبدیل ۳۶ مجموعهٔ MongoDB به ۷۴ جدول Postgres بود که نیازمند توجه دقیق به روابط، ایندکسگذاری و بهینهسازی کوئریها بود.
انتخاب ORM مناسب: Sequelize در برابر Prisma
یکی از نخستین تصمیمهای مهم، انتخاب یک ابزار ORM (نگاشت شیء-رابطهای) بود. از آنجا که کدبیس ما پیشتر برای استفاده از Mongoose با MongoDB طراحی شده بود، استفاده از ORM مسیری را با هموارترین گذار فراهم میکرد.
تحلیل نیازمندیها
پس از تحلیل دقیق نیازهای پروژه، معیارهای اساسی زیر را برای هر راهحل ORM تعیین کردم:
- باید یک بستهٔ JavaScript باشد (بیشتر کد ما با JavaScript نوشته شده بود)
- باید از Postgres و بیشتر قابلیتهای آن پشتیبانی کند
- عملکرد باید حداقل همسطح Mongoose یا بهتر از آن باشد
- باید متنباز و نگهداریشده باشد
گزینهها
پس از پژوهش گسترده، به دو مدعی اصلی رسیدم: Sequelize و Prisma. من محیطهای آزمایشی جامعی را با استفاده از Docker برای Postgres ایجاد کردم و بزرگترین و پیچیدهترین مجموعهدادهٔ ما را از ساختار سندی به ساختار جدولی تبدیل کردم.
روش آزمایش
برای هر ORM، عملکرد را در عملیاتهای حیاتی اندازهگیری کردم:
- زمان ایجاد یک رکورد
- زمان بهروزرسانی یک رکورد
- زمان بهروزرسانی رکوردهای تو در تو (روابط و مقادیر کلید-مقدار JSON)
- زمان حذف یک رکورد
- زمان پرسوجو/دریافت یک رکورد
تصمیم: Sequelize
حدود ۱۵ مهٔ ۲۰۲۳ تصمیم گرفتم که Sequelize برای مورد استفادهٔ ما ORM بهتری است. دلیلش اینها بود:
مزایای Sequelize:
- واقعاً متنباز است و توسط یک استارتاپِ تأمین مالیشده نگهداری نمیشود
- از بیشتر قابلیتهای Postgres پشتیبانی میکند
- عملکرد بهتر، بهویژه در مقایسه با Prisma
- اکوسیستم بالغ با بیش از ۱۰ سال توسعه
- نمایش انعطافپذیر مدل/اسکیما با استفاده از کلاسهای JavaScript
- پشتیبانی از joinها و گزینههای فیلترسازی پیچیده، از جمله Regex
نتایج عملکرد:
در آزمایشهای من، Sequelize بهطور قابلتوجهی از Prisma بهتر عمل کرد. برای رکوردهای مجموعهدادهٔ بزرگ ما:
- Sequelize: حدود ۲٫۲۶ ثانیه برای هر رکورد
- Prisma: حدود ۱۱٫۲۱ ثانیه برای هر رکورد
Prisma برای مورد استفادهٔ ما تقریباً ۵ برابر کندتر از Sequelize بود. علاوه بر این، حذف یک رکورد از بزرگترین مجموعهدادهٔ ما با Prisma نزدیک به ۴ دقیقه طول میکشید که برای نیازهای ما قابلقبول نبود.
چالشهای Sequelize:
- نمایش مدل پیچیدهتر و حجیمتر (۵۶۴ خط در برابر ۲۶۲ خط برای Mongoose)
- نحو گیجکننده در برخی موارد
- پیچیدگی مهاجرت پایگاهداده
- مستندات کمتر جامع در مقایسه با Prisma
مقایسهٔ مزایا و معایب Sequelize و Prisma
برای ارائهٔ تصویری کاملتر از اینکه چرا Sequelize را انتخاب کردم، میخواهم مزایا و معایب تفصیلیای را که در ارزیابیام برای هر دو ORM گردآوری کردم، به اشتراک بگذارم. همچنین بررسی کردم که آنها از نظر نمایش اسکیما و پشتیبانی جامعه تا تاریخ ۱۵ مهٔ ۲۰۲۳ چگونه در برابر هم قرار میگرفتند. این بررسی عمیقتر به تثبیت انتخابم کمک کرد، و امیدوارم برای هر کس دیگری که با تصمیمی مشابه روبهروست مفید باشد.
مزایای Sequelize:
- تابع sync() را دارد که بهطور خودکار جدولها را برای شما ایجاد و مدیریت میکند و در زمان زیادی از کار دستی صرفهجویی میکند.
- میتواند joinهای پیچیده برای دادههای تو در تو را مدیریت کند، که برای ساختار STMS حیاتی بود.
- از طیف گستردهای از گزینههای فیلترسازی، از جمله Regex، پشتیبانی میکند و در کوئریها انعطافپذیری میدهد.
- نمایش مدل/اسکیما در JavaScript خام و با استفاده از کلاسها انجام میشود که بسیار قابلسفارشیسازی برای نیازهای خاص هستند.
- اتصالات پایگاهداده را بهصورت یکپارچه مدیریت میکند، از جمله پشتیبانی از چندین اتصال خواندنی.
- از کوئریهای SQL خام پشتیبانی میکند برای زمانی که لازم است به عمق کار بروید.
- آمار جامعه تا ۱۵ مهٔ ۲۰۲۳: در NPM، آخرین بهروزرسانی ۱۴ روز پیش با ۱٬۵۰۵٬۸۳۵ دانلود هفتگی؛ در GitHub، آخرین بهروزرسانی دیروز با ۴٫۲ هزار Fork و ۲۷٫۹ هزار Star. این پروژه بیش از ۱۰ سال است که با مجوز MIT متنباز بوده، بنابراین مطمئنم همینطور باقی خواهد ماند.
معایب Sequelize:
- نمایش مدل/اسکیما میتواند بسیار پیچیده و حجیم شود. برای نمونه، در حالی که نمایش Mongoose برای مجموعهدادهٔ بزرگ ما حدود ۲۶۲ خط بود (شامل فاصلهها)، همان مجموعهداده در Sequelize به ۵۶۴ خط رسید.
- نحو در برخی سناریوها میتواند گیجکننده و پیچیده باشد که گاهی مرا کند میکرد.
- مهاجرت یا ویرایش پایگاهداده دردسرساز است. حتی با اینکه sequelize-cli اسکریپتهای مهاجرت تولید میکند، باز هم دستوپاگیر است، هرچند متوجه شدهام این یک نقطهدرد رایج در بیشتر ORMهاست.
- مستندات عالی نیست، هرچند در حال بهتر شدن است. خوشبختانه ابزارهایی مانند ChatGPT بهدلیل تاریخچهٔ طولانی Sequelize درک خوبی از آن دارند که به پر کردن شکافها کمک کرد.
- به اندازهٔ Prisma به نوع حساس نیست، که میتواند در برخی پروژهها مشکلساز شود.
- پشتیبانی TypeScript محدود است، هرچند این برای STMS مسئلهای نبود، اما برای دیگران میتواند عامل تعیینکننده باشد.
مزایای Prisma:
- از زبان اسکیماى خودش استفاده میکند، که ایجاد مدل را تمیزتر و موجزتر میسازد. برای مقایسه، در حالی که Mongoose برای مجموعهدادهٔ بزرگ ما ۲۶۲ خط گرفت، Prisma آن را فقط در ۲۲۱ خط انجام داد.
- همراه با یک ابزار CLI عرضه میشود که ایجاد و مهاجرت پایگاهداده را ساده میکند، و این بهترین چیزی است که تا اینجا از یک ORM دیدهام، حتی اگر کامل نباشد.
- از کوئریهای SQL خام پشتیبانی میکند و در مواقع لازم انعطافپذیری ارائه میدهد.
- نحو کد تمیز و در مقایسه با Sequelize سادهتر برای فهمیدن است، و یادگیری آن را آسانتر میکند.
- از طریق کلاینت خود، بهصورت خودکار query builderها را برای Node.js و TypeScript تولید میکند که نکتهٔ خوبی است.
- مستندات عالی و تمیزی دارد. ChatGPT بهروزرسانیشدهترین منبع دربارهٔ Prisma نیست، اما مستندات رسمی اغلب این کمبود را جبران میکرد.
- آمار جامعه تا ۱۵ مهٔ ۲۰۲۳: در NPM، آخرین بهروزرسانی ۶ روز پیش با ۱٬۳۴۴٬۷۰۵ دانلود هفتگی؛ در GitHub، آخرین بهروزرسانی ۳ ساعت پیش با ۱٫۱ هزار Fork و ۳۱٫۳ هزار Star.
معایب Prisma:
- از فیلترسازی Regex برای Postgres پشتیبانی نمیکند، هرچند جایگزینهایی مانند “contains”، “includes” و “startsWith” ارائه میدهد.
- عملکرد در آزمایشهای من یک مشکل بزرگ بود. ایجاد رکوردها برای مجموعهدادهٔ بزرگ ما با Prisma حدود ۱۱٫۲۱ ثانیه برای هر رکورد زمان میبرد، در مقایسه با ۲٫۲۶ ثانیهٔ Sequelize، یعنی حدود ۵ برابر کندتر.
- حذف یک رکورد واحد از مجموعهدادهٔ بزرگ نزدیک به ۴ دقیقه طول میکشید که برای نیازهای ما مانع جدی بود.
- حتی با یک مقایسهٔ منصفانه روی مجموعهدادهای با رابطهٔ پیچیده و سهلایه، Sequelize در حذفها بهطور قابلتوجهی سریعتر بود.
- Prisma توسط یک استارتاپ با ۵۶٫۵ میلیون دلار سرمایهگذاری پشتیبانی میشود. در حالی که کد اصلی ORM آن با Apache-2.0 متنباز است، من نسبت به تغییرات احتمالی مجوز در آینده محتاطم، مشابه آنچه برای MongoDB رخ داد.
این مقایسههای دقیق روشن کرد که Sequelize بهتر با نیازهای STMS هماهنگ است، بهویژه از نظر عملکرد و قابلیت اعتماد بلندمدت. اما فکر کردم اینگونه باز کردن موضوع شاید به دیگرانی که با همین انتخاب برای پروژههایشان دستوپنجه نرم میکنند کمک کند.
فرآیند مهاجرت
تبدیل ساختار داده
تبدیل از ساختار سندیِ MongoDB به ساختار رابطهایِ Postgres نیازمند برنامهریزی دقیق بود. من مجبور بودم:
- تحلیل روابط: مشخص کنم اسناد MongoDB چگونه به یکدیگر مرتبط هستند و روابط کلید خارجی مناسبی طراحی کنم
- نرمالسازی دادهها: اسناد تو در تو را در صورت مناسب به جدولهای جداگانه تقسیم کنم
- حفظ ویژگیهای JSON: برای دادههای واقعاً بدونساختار که لازم بود انعطافپذیر باقی بمانند، از ستونهای JSONB استفاده کنم
- طراحی ایندکسها: ایندکسهای مناسبی برای عملکرد کوئری ایجاد کنم
راهحلهای سفارشی
این مهاجرت نیازمند توسعه چندین راهحل سفارشی بود:
1. اسکریپتهای مهاجرت داده من اسکریپتهای جامعی ایجاد کردم تا:
- دادهها را از مجموعههای MongoDB استخراج کنم
- ساختار سندی را به قالب رابطهای تبدیل کنم
- دادهها را با روابط صحیح در جدولهای Postgres وارد کنم
2. لایه سازگاری API برای حفظ عدم قطعی کامل سرویس، لایهای سازگارکننده ساختم که میتوانست:
- درخواستها را بسته به وضعیت مهاجرت به MongoDB یا Postgres هدایت کند
- در طول دوره انتقال، سازگاری دادهها را تضمین کند
- مکانیزمهای جایگزین فراهم کند
3. میانافزار سفارشی میانافزاری توسعه دادم تا تفاوتهای نحوهٔ مدیریت برخی عملیات در MongoDB و Postgres را رسیدگی کند و اطمینان دهد که endpointهای موجود API بدون نیاز به تغییر به کار خود ادامه میدهند.
غلبه بر چالشهای فنی
رسیدگی به روابط پیچیده
یکی از بزرگترین چالشها تبدیل اسناد تعبیهشدهٔ MongoDB به روابط Postgres بود. برای مثال، یک سند واحد MongoDB ممکن بود شامل موارد زیر باشد:
- ویژگیهای پایه
- اشیای تو در تو که موجودیتهای مرتبط را نمایش میدهند
- آرایههایی از اسناد تعبیهشده
این موارد باید با دقت به شکل زیر تجزیه میشدند:
- جدولهای اصلی برای موجودیتهای اصلی
- جدولهای واسط برای روابط چندبهچند
- روابط کلید خارجی برای ارتباطهای یکبهچند
بهینهسازی کوئری
الگوهای کوئری MongoDB مستقیماً به SQL ترجمه نمیشوند. من مجبور بودم:
- پایپلاینهای پیچیدهٔ تجمیع را به joinهای SQL بازنویسی کنم
- ایندکسها را برای الگوهای کوئری جدید بهینه کنم
- اطمینان حاصل کنم عملکرد کوئری با عملکرد MongoDB برابری میکند یا از آن فراتر میرود
یکپارچگی داده
تضمین یکپارچگی داده در طول مهاجرت مستلزم موارد زیر بود:
- اسکریپتهای اعتبارسنجی جامع
- رویههای بازگشت
- همگامسازی بلادرنگ دادهها در طول دورههای انتقال
نتایج و تأثیر
مهاجرت STMS از MongoDB به Postgres با موفقیت و بدون هیچگونه قطعی، در حالی که تقریباً همهٔ ویژگیها و قابلیتها حفظ شدند، تکمیل شد. نتایج از انتظار فراتر رفت:
بهبودهای عملکردی:
- عملکرد کوئری برای کوئریهای رابطهای پیچیده بهبود یافت
- سازگاری و یکپارچگی داده بهتر شد
- استفاده از فضای ذخیرهسازی کارآمدتر شد
مزایای عملیاتی:
- قابلیتهای پایش و اشکالزدایی بهبود یافت
- یکپارچگی بهتر با ابزارهای مبتنی بر SQL موجود eBay
- رویههای پشتیبانگیری و بازیابی بهبود یافت
تأثیر بر تیم:
- دانش تیم دربارهٔ پایگاههای دادهٔ رابطهای افزایش یافت
- الگوهایی برای مهاجرتهای آیندهٔ پایگاه داده ایجاد شد
- ابزارها و فرایندهای قابلاستفادهٔ مجدد ساخته شد
مهارتهای فنی کسبشده
این پروژه تخصص فنی من را بهطور چشمگیری گسترش داد:
فناوریهای پایگاه داده:
- درک عمیق از قابلیتها و بهینهسازی Postgres
- بهینهسازی کوئری SQL و تنظیم عملکرد
- الگوهای طراحی پایگاه داده و نرمالسازی
- پیکربندیهای پایگاه دادهٔ اصلی-آمادهبهکار
ابزارهای توسعه:
- ORM سیکوئلایز و ساخت کوئری
- راهبردهای مهاجرت پایگاه داده
- روششناسیهای آزمون عملکرد
- اعتبارسنجی داده و بررسی یکپارچگی
الگوهای معماری:
- راهبردهای مهاجرت بدون قطعی
- لایههای سازگاری API
- الگوهای انتزاع پایگاه داده
- سامانههای پایش و هشدار
رشد شخصی و حرفهای
این پروژهٔ مهاجرت برای رشد حرفهای من دگرگونکننده بود. مرا وارد قلمروی ناشناختهای کرد و نیازمند موارد زیر بود:
مهارتهای رهبری:
- رهبری یک پروژهٔ فنی پیچیده بدون تجربهٔ قبلی
- گرفتن تصمیمهای معماری حیاتی تحت فشار
- هماهنگی با چندین تیم و ذینفع
تواناییهای حل مسئله:
- شکستن مسائل پیچیده به اجزای قابلمدیریت
- توسعهٔ راهحلهای خلاقانه برای چالشهای بیسابقه
- ایجاد تعادل میان چندین نیاز و محدودیتِ متضاد
ارتباطات و کار تیمی:
- توضیح مفاهیم فنی به ذینفعان غیر فنی
- مستندسازی فرایندها و تصمیمها برای ارجاع در آینده
- راهنمایی اعضای تیم دربارهٔ فناوریها و الگوهای جدید
درسهای آموختهشده
درسهای فنی
- انتخاب پایگاه داده مهم است: انتخاب بین NoSQL و SQL باید بر اساس موارد استفادهٔ مشخص و نیازهای بلندمدت باشد
- آزمون عملکرد حیاتی است: مزیتهای نظری همیشه به بهبود عملکرد در دنیای واقعی تبدیل نمیشوند
- برنامهریزی مهاجرت: برنامهریزی و آزمون جامع برای مهاجرتهای پیچیده ضروری است
- سرمایهگذاری در ابزارسازی: ساخت ابزار مناسب از ابتدا زمان قابلتوجهی صرفهجویی میکند و خطاها را کاهش میدهد
درسهای مدیریت پروژه
- ارتباط با ذینفعان: بهروزرسانیهای منظم و ارتباط شفاف از سوءتفاهم جلوگیری میکند
- مدیریت ریسک: داشتن طرحهای جایگزین و رویههای بازگشت ضروری است
- مدیریت زمانبندی: برای چالشهای پیشبینینشده و منحنیهای یادگیری، زمان حاشیه در نظر بگیرید
- مستندسازی: مستندسازی دقیق امکان انتقال دانش و نگهداری آینده را فراهم میکند
نتیجهگیری
مهاجرت STMS از MongoDB به Postgres بهعنوان چالشبرانگیزترین و پاداشبخشترین مسئلهٔ فنیای که در حرفهام حل کردهام، برجسته است. این کار نهتنها به تخصص فنی، بلکه به رهبری، برنامهریزی و سازگاری نیز نیاز داشت. موفقیت پروژه نشان داد که با برنامهریزی مناسب، آزمونهای کامل و تعهد به برتری، حتی پیچیدهترین چالشهای فنی نیز قابل غلبه هستند.
این تجربه رویکرد من به مهندسی نرمافزار را بهطور بنیادی تغییر داد و بر اهمیت موارد زیر تأکید کرد:
- درک زمینه و الزامات کامل پیش از گرفتن تصمیمهای فنی
- صرف زمان برای ابزارسازی و آزمون مناسب
- حفظ ارتباط شفاف در طول پروژههای پیچیده
- آمادگی برای یادگیری فناوریها و رویکردهای جدید در صورت نیاز
موفقیت این مهاجرت نهتنها قابلیتهای STMS را بهبود داد، بلکه الگوها و فرایندهایی ایجاد کرد که همچنان به پروژههای زیرساختی eBay سود میرسانند. این تجربه باور من را تقویت کرد که پذیرفتن چالشهای ناشناخته و موفق شدن در عبور از آنها، کلید رشد شخصی و حرفهای است.
با نگاه به گذشته، این پروژه یک نقطهٔ عطف در حرفهٔ من را نشان میدهد و مرا از توسعهدهندهای که راهحلها را پیادهسازی میکند به مهندسی تبدیل کرد که میتواند ابتکارات فنی پیچیده را طراحی و رهبری کند. اعتمادبهنفس و مهارتهایی که از این تجربه به دست آوردم، همچنان رویکرد من را در برابر چالشها و فرصتهای جدید در مهندسی نرمافزار هدایت میکنند.