از الگوهای دسترسی تا معماری: بهینه‌سازی سرویس تصاویر آگهی‌ها

تصاویر آگهی‌ها یکی از مهم‌ترین بخش‌های دیوار هستند. کاربران هم زمان ثبت آگهی جدید و هم گشت و گذار در دیوار با سرویس مدیریت تصاویر در ارتباط هستند. اگر این سرویس کیفیت خوبی نداشته باشد هم آگهی‌های کمتری ثبت می‌شود و هم برروی تجربه کاربر زمان گشت و گذار در دیوار تاثیر منفی می‌گذارد.

در یکی از مقاله‌های قدیمی توضیح دادیم سرویس‌ تصاویر دیوار را چطور طراحی کردیم و چه تصمیم‌هایی گرفتیم که پیشنهاد میکنیم قبل از مطالعه‌ی این مقاله، «دیوار چگونه از میلیون‌ها عکس نگهداری می‌کند؟» را بخوانید.

الگوهای دسترسی تصاویر آگهی‌های دیوار

منظور از الگوهای دسترسی داده (Data Access Patterns) الگو‌هایی هستند که کاربر می‌تواند یک داده‌ ، در این مثال یک تصویر، را ذخیره، به‌روز یا دریافت کند. این الگوها تحت تأثیر میزان و نحوه‌ی درخواست تصاویر به‌وسیله‌ی کاربران قرار دارند. به عنوان مثال، دریافت تصاویر پروفایل یک کاربر در یک شبکه‌ی اجتماعی رابطه مستقیم با دنبال‌کنندگان و فعالیت کاربر دارد. در یک سرویس لیستینگ مشابه دیوار نیز الگوی دسترسی کاربران براساس تازگی (Recency) آگهی می‌باشد، به طوری که تصاویر مربوط به آگهی‌های جدیدتر بیشتر از تصاویر پست‌های قدیمی‌تر مشاهده می‌شوند.

در دیوار، مفهوم «تازگی» (ًRecency) نقش مهمی در نحوه تعامل کاربران با آگهی‌ها دارد. معمولاً آگهی‌هایی که بیشتر مشاهده می‌شوند، آگهی‌های جدیدتر هستند، چرا که آگهی‌های جدید در بالای لیست می‌آیند و در معرض دید بیشتری هستند. این به این معناست که یک زیرمجموعه کوچک از تصاویر، که مربوط به آگهی‌های جدیدتر هستند، به صورت مکرر دیده می‌شوند در حالی که بیشتر تصاویر ذخیره شده، که مربوط به آگهی‌های قدیمی‌تر هستند، کمتر دیده می‌شوند. درک این الگو به ما این امکان را می‌دهد که استراتژی ذخیره‌سازی خود را بهینه کنیم؛ به این صورت که تصاویر پست‌های جدید را در یک انبار داده سریع‌تر نیز نگه داریم تا دسترسی سریع‌تری داشته باشیم و تجربه کاربری بهتری فراهم شود. به این ترتیب، تصاویر کمتر استفاده شده را می‌توان در یک انبار داده کندتر اما مقرون به صرفه‌تر ذخیره کرد تا بین عملکرد و هزینه تعادل برقرار شود.

تغییر و طراحی جدید سرویس تصویرها

در طراحی قبلی، پیش از ثبت آگهی، عکس‌های آپلود شده را در باکتی با نام temp نگهداری می‌کردیم. پس از ثبت آگهی، از این عکس سه کپی گرفته می‌شد و عکس فعلی از باکت temp پاک و در باکت اصلی عکس‌های آگهی نگهداری می‌شد.

  • عکس‌های ‌Thumbnail: عکس‌های بند انگشتی با سایز حدوداً ۲۰۰ در ۲۰۰ پیکسل، در صفحهٔ اول دیوار نمایش داده می‌شود.
  • عکس‌های Post: عکس‌های پست در صفحهٔ آگهی نمایش داده می‌شود. این عکس‌ها با‌نسبتِ ابعاد عکس اصلی، تا ۱۵۰۰ پیکسل کوچک می‌شوند. همچنین، بر روی این عکس‌ها واترمارک قرار می‌گیرد و کیفیت آن‌ها برای کاهش حجم، ۱۰ درصد کم می‌شود.
  • عکس‌های Manage: عکس‌هایی که فقط آگهی‌گذار می‌تواند آن‌ها را در صفحه مدیریت آگهی خود ببیند. این عکس‌ها با نسبت ابعاد عکس اصلی، تا ۳۰۰۰ پیکسل ذخیره می‌شود. این عکس‌ها بدون واترمارک و با کیفیت اصلی نگهداری می‌شود.

اما این طراحی برای ما مشکلاتی می‌ساخت.

  • وابستگی ثبت آگهی به فرآیند پردازش عکس‌ها: این وابستگی یک‌طرفه باعث می‌شد زمانی که سرویس عکس کند بود و یا پاسخ‌گو نبود، سرویس ثبت آگهی نیز دچار مشکل شود. همچنین پردازش‌آگهی‌هایی که عکس‌های زیادی داشتند، باعث کند شدن ثبت آن آگهی‌ها می‌شد.
  • حجم زیاد عکس‌ها به‌خاطر چند کیفیت بودن: از هر عکس سه نسخه نگهداری می‌شد که حجم زیادی از فضای ذخیره‌سازی را مصرف می‌کرد.

با تحقیق درباره‌ی ابزارهای موجود و بر اساس بنچ‌مارک‌ها تصمیم گرفتیم از ایمیج پروکسی استفاده کنیم. ایمیج پروکسی ابزاری‌ست که پردازش‌های تصویر را به صورت برخط انجام می‌دهد و عکسی را در حافظه نگه‌ نمی‌دارد. از مهم‌ترین ویژگی‌های ایمیج پروکسی این است که عکس‌ها را در لحظه می‌خواند و پردازش‌های تصویر (مثل کاهش کیفیت تصویر، کاهش سایز تصویر، اضافه کردن واترمارک و…) را انجام می‌دهد.

با استفاده از ایمیج پروکسی دیگر نیاز به نگهداری سه نسخه از تصویرها نیست، بلکه یک نسخه از آن‌ها نگهداری می‌شود و پردازش تصویرها به کمک ایمیج پروکسی صورت می‌گیرد. با تنظیم سه حالت Thumbnail، Post و Manage عکس‌های مورد نظر توسط ایمیج پروکسی پردازش می‌شود. با این ابزار، به جای نگهداری سه نسخه از عکس‌ها، عکس‌ها را در موقع درخواست پردازش کرده و آن‌ها را نمایش می‌دهیم.

پس با این تغییر، دو اتفاق مهم می‌افتد:

  • دیگر فرآیند پردازش تصاویر بر تجربهٔ کاربر در زمان ثبت آگهی تاثیر منفی نمی‌گذارد.
  • به‌جای پیش‌پردازش تصویرها و نگهداری نسخه‌های مختلف از هر عکس، فقط نسخهٔ اصلی را نگهداری می‌کنیم؛ اما هربار، براساس نیاز، زمان درخواست هر تصویر باید پردازش متناسب را داشته باشیم.

حال این سوال پیش می‌آید که با استفاده از ایمیج پروکسی حجم بسیار زیادی از فضای ذخیره‌سازی خودمان را کاهش دادیم و این بار را بر دوش پردازنده‌ها انداختیم. چطور فشار را بر روی پردازنده‌ها کمتر کنیم؟

کش کردن خروجی Image Proxy

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

همیشه عکس‌ها یا آگهی‌های جدید بیشتر از آگهی‌های قدیمی دیده می‌شود. از این رو با استفاده از CDN می‌توانیم عکس‌های اخیر را کش کنیم و عکس‌های پیشین را نیز به خودی خود از نود‌های CDN پاک کنیم. با این کار، با استفاده از یک سیستم خودکار کش، با فضای کمتر و مدیریت راحت‌تر، می‌توان عکس‌ها را بدون پردازش نمایش داد؛ بدون آنکه نیاز باشد از همه‌ٔ عکس‌ها برای مدت طولانی سه نسخه نگهداری کنیم.

همچنین یک الگوی دست‌رسی دیگر به داده‌ها در دیوار براساس موقعیت‌ جغرافیایی آگهی‌هاست. یعنی اگر شخصی در شهر مشهد آگهی‌ای ثبت می‌کند، اکثر بازدید‌های آن آگهی نیز از کابران مشهد خواهد بود. استفاده از CDN که لبه‌های آن در موقعیت‌های جغرافیایی مختلف هم توزیع شده است باعث می‌شود تصاویر آگهی‌های هر شهر، بیشتر در لبه‌ی آن شهر (یا نزدیک ترین لبه به آن) درخواست شوند و این روند باعث بهینه‌سازی کش‌ تصاویر آگهی‌ها می‌شود.

لبه‌های CDN هرکدام ممکن است برای بارگیری عکس‌ها یک درخواست به ایمیج پروکسی بزنند. پس برای سه نسخهٔ عکس و به تعداد لبه‌های CDN، درخواست‌های زیادی به سوی ایمیج پروکسی روانه می‌شود. برای جلوگیری از بار اضافه بر پردازنده‌ها، از کش پروکسی استفاده کردیم. به این ترتیب، فقط برای اولین درخواست از CDN، درخواست به ایمیج پروکسی می‌رسد و باقی درخواست‌ها به‌وسیله‌ی کش پروکسی پاسخ داده می‌شود. از NginX به عنوان کش پروکسی استفاده کردیم و کش‌ها را با Policy پاک کردن عکس‌های قدیمی تنظیم کردیم.

به طور کلی دیاگرام زیر معماری جدید سرویس‌ عکس دیوار را نشان می‌دهد.

این راهکار کمک می‌کند تا هر عکس فقط یک‌بار به ایمیج پروکسی و در نهایت یک بار نیز به آبجکت استوریج درخواست بفرستند. دلیل وجود کش پروکسی دقیقاً همین است: که هر لبه‌ی CDN یک درخواست به ایمیج پروکسی ارسال نکند، بلکه فقط همان درخواست اول ارسال شود و باقی درخواست‌ها را کش پروکسی جواب دهد. نتیجهٔ این تمهید باعث شد تا عکس‌های پر بازدید اخیر در کش‌ها بمانند و درخواست‌ها یا از لبهٔ CDN و یا به‌وسیله‌ی کش پروکسی پاسخ داده شود. در نهایت نیز فقط یک نسخه از عکس نگهداری می‌شود.

نتایج این طراحی چه بود؟

با نگاهی متفاوت این سیستم را بازطراحی کردیم و با استفاده از منابع کمتر، توانستیم زمان پاسخ‌دهی بهتر و کیفیت خدمات بالاتری را به دست آوریم. در نتیجه، بیش از ۵۸ درصد از فضای آبجکت استوریج کاهش داشتیم. همچنین در سرویس جدید با بهینه‌سازی ۴۶ درصد از منابع پردازشی (cpu) و ۳۴ درصد از منابع حافظه‌ (memory) صرفه‌جویی کردیم. این صرفه‌جویی‌ها روی هم رفته حدود ۶ درصد ماهانه در آن زمان از هزینه‌ی زیرساخت ما کم کرد.

با معماری جدید، پردازش‌های تصاویر دیگر در زمان ثبت آگهی انجام نمی‌شدند و تأثیر منفی بر تجربه کاربری ثبت آگهی نداشتند. این تغییر باعث افزایش تعداد آگهی‌های عکس‌دار در دیوار شد و در نهایت، بر کیفیت آگهی‌ها نیز تأثیر مثبت گذاشت.

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