پست

الگوهای رایج مصاحبه‌های طراحی سیستم

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

الگوهای رایج مصاحبه‌های طراحی سیستم

الگوهای رایج

رایج‌ترین الگوهای مصاحبه طراحی سیستم را در این فصل مرور می‌کنیم. مدیران و مهندسان ارشد شرکت‌های FAANG این مطالب را گردآوری کرده‌اند.

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

توانایی تشخیص و به‌کار بردن الگوها مهارتی است که در مصاحبه طراحی سیستم اغلب مهندسان ارشد را از مهندسان جونیورتر جدا می‌کند. الگوها به شما می‌گویند چه چیزی مهم است و چه چیزی نه. همچنین حالت‌های خرابی رایج را از قبل نشانتان می‌دهند و لازم نیست وسط مصاحبه آن‌ها را از صفر کشف کنید.

ساختار کلی

آنچه در ادامه می‌آید چند الگوی رایج برای ساخت سیستم‌هاست. این الگوها مستقل از هم نیستند و اغلب برای ساخت یک سیستم چند الگو را با هم به کار می‌برید. در هر کدام از تحلیل‌های مسئله (problem breakdown) الگوهای مورد استفاده را مشخص می‌کنیم تا این اشتراک‌ها را ببینید. درباره بررسی‌های عمیق و تله‌های رایج هم می‌توانید مطالعه کنید.

به‌روزرسانی بلادرنگ

در بسیاری از سیستم‌ها باید به‌روزرسانی‌ها را بلادرنگ به کاربر برسانید. برای APIهای هم‌زمان (synchronous) کافی است بعد از تکمیل درخواست پاسخ را برگردانید. اما در سیستم‌هایی مثل اپلیکیشن‌های چت، اعلان‌ها یا داشبوردهای زنده باید هر بار که اتفاقی افتاد، آن را به کاربر push کنید.

برای پیاده‌سازی به‌روزرسانی بلادرنگ باید چند تصمیم بگیرید. اول باید پروتکل را انتخاب کنید. Polling ساده با HTTP ساده‌ترین گزینه است، اما کارآمدترین نیست. Server-Sent Events (SSE) و WebSocket برای به‌روزرسانی بلادرنگ ساخته شده‌اند، اما راه‌اندازی زیرساختشان سخت‌تر است. برای بررسی عمیق‌تر انتخاب پروتکل، مفهوم اصلی «مبانی شبکه» را بخوانید. توصیه کلی ما این است که با HTTP Polling شروع کنید و تا وقتی نیازهایتان را برآورده می‌کند، همان را نگه دارید. بعد از آن می‌توانید SSE یا WebSocket را در نظر بگیرید.

سمت سرور هم گزینه‌های متعددی دارید. سرویس‌های Pub/Sub (انتشار/اشتراک) راه رایجی برای جدا کردن ناشر و مشترک هستند. این روش در تحلیل WhatsApp استفاده شده است. در موقعیت‌هایی که پردازش سنگین‌تری لازم است، می‌توانید از سرورهای stateful روی یک Consistent Hash Ring یا پیکربندی‌های دیگر استفاده کنید. این روش در تحلیل Google Docs به کار رفته است.

درباره همه این گزینه‌ها در الگوی «Pushing Realtime Updates» به‌طور مفصل صحبت کرده‌ایم.

مدیریت وظایف طولانی‌مدت

بسیاری از عملیات در سیستم‌های توزیع‌شده برای پردازش هم‌زمان خیلی طولانی‌اند. مثل انکود کردن ویدیو، تولید گزارش، عملیات انبوه یا هر کاری که بیش از چند ثانیه طول بکشد. الگوی مدیریت وظایف طولانی‌مدت این عملیات را به دو بخش تقسیم می‌کند، تأیید فوری و پردازش در پس‌زمینه.

وقتی کاربران کار سنگینی ارسال می‌کنند، وب‌سرور بلافاصله درخواست را تأیید می‌کند، یک job به صف (مثل Redis یا Kafka) می‌فرستد و در چند میلی‌ثانیه یک job ID برمی‌گرداند. بعد فرایندهای worker جداگانه مدام job ها را از صف برمی‌دارند و کار اصلی را اجرا می‌کنند. نتیجه این است که زمان پاسخ به کاربر کوتاه می‌ماند، وب‌سرورها و workerها مستقل مقیاس می‌یابند و خطاها ایزوله می‌شوند.

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

فناوری‌های کلیدی این الگو Message Queue برای هماهنگی job ها و worker pool برای پردازش هستند. باید ردیابی وضعیت job، تلاش مجدد (retry) و سناریوهای شکست را هم مدیریت کنید. مثلاً Dead Letter Queue برای پیام‌های معیوب (poison message) لازم است.

الگوی «مدیریت وظایف طولانی‌مدت» جزئیات کامل worker poolهای ناهمزمان، صف job ها و مدیریت شکست را پوشش می‌دهد.

مدیریت رقابت (Contention)

وقتی چند کاربر هم‌زمان سعی می‌کنند به یک منبع دسترسی پیدا کنند، مثل رزرو آخرین بلیت کنسرت یا پیشنهاد قیمت برای یک کالای مزایده، به مکانیزم‌هایی نیاز دارید که جلوی race condition را بگیرد و سازگاری داده را حفظ کند. این الگو چالش‌های هماهنگی در سیستم‌های توزیع‌شده را پوشش می‌دهد.

راه‌حل‌ها از رویکردهای سطح پایگاه داده، مثل قفل بدبینانه (pessimistic locking) و کنترل هم‌زمانی خوش‌بینانه (optimistic concurrency control)، تا مکانیزم‌های هماهنگی توزیع‌شده را شامل می‌شود. نکته کلیدی این است که بدانید چه وقت عملیات اتمیک و تراکنش مناسب‌اند و چه وقت باید از استراتژی‌های قفل صریح استفاده کنید. در سیستم‌های توزیع‌شده ممکن است به قفل توزیع‌شده، پروتکل two-phase commit یا سریال‌سازی مبتنی بر صف نیاز داشته باشید.

مصالحه‌ها شامل تضمین کارایی در برابر سازگاری، و راه‌حل ساده پایگاه داده در برابر هماهنگی توزیع‌شده پیچیده است. بیشتر مسائل با یک راه‌حل تک‌پایگاهی شروع می‌شوند و قبل از رفتن سراغ رویکردهای توزیع‌شده، باید مقیاس بگیرند.

پایگاه‌های داده حول محور مسئله رقابت ساخته شده‌اند. وقتی داده را بین چند پایگاه داده جدا می‌کنید، همه چالش‌هایی را که پایگاه‌های داده برای حلشان ساخته شده‌اند، خودتان به عهده می‌گیرید. گاهی این کاملاً درست است، اما مراقب باشید زودتر از موقع این کار را نکنید. مصاحبه‌گران دوست دارند عمیق‌تر بپرسند و ببینند آیا واقعاً می‌دانید با جدا کردن داده‌ها چه چیزهایی را از دست می‌دهید.

الگوی «Dealing with Contention» قفل‌ها، تراکنش‌ها و تکنیک‌های هماهنگی توزیع‌شده را به‌طور عمیق بررسی می‌کند.

مقیاس‌دهی خواندن (Scaling Reads)

وقتی برنامه از صدها کاربر به میلیون‌ها کاربر می‌رسد، ترافیک خواندن اغلب اولین گلوگاه می‌شود. نوشتن داده را تولید می‌کند و خواندن آن را مصرف می‌کند، و ترافیک خواندن معمولاً خیلی سریع‌تر از ترافیک نوشتن رشد می‌کند. الگوی Scaling Reads نشان می‌دهد چطور درخواست‌های خواندن با حجم بالا را با بهینه‌سازی پایگاه داده، مقیاس افقی و کش هوشمند مدیریت کنید.

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

راه‌حل یک مسیر طبیعی را دنبال می‌کند. ابتدا کارایی خواندن را در خود پایگاه داده با ایندکس‌گذاری و denormalization بهینه کنید. بعد با read replica ها افقی مقیاس بدهید. در مرحله آخر لایه‌های کش خارجی مثل Redis و CDN را اضافه کنید.

ملاحظات کلیدی شامل مدیریت Cache Invalidation، مدیریت replication lag در replica های خواندنی، و مواجهه با hot key هاست. hot key یعنی میلیون‌ها کاربر هم‌زمان یک محتوای محبوب را درخواست می‌کنند.

الگوی «Scaling Reads» درباره استراتژی‌های ایندکس‌گذاری، read replica و الگوهای Cache Invalidation اطلاعات بیشتری می‌دهد.

مقیاس‌دهی نوشتن (Scaling Writes)

وقتی برنامه از صدها نوشتن در ثانیه به میلیون‌ها می‌رسد، تک‌تک سرورهای پایگاه داده و سیستم‌های ذخیره‌سازی محدودیت سخت دارند. الگوی Scaling Writes گلوگاه‌های نوشتن را با تقسیم‌بندی (sharding)، دسته‌بندی (batching) و مدیریت هوشمند بار حل می‌کند.

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

برای مدیریت موج‌های ناگهانی می‌توانید از صف نوشتن برای بافر کردن اوج‌های موقت استفاده کنید. یا load shedding را پیاده کنید تا در زمان بار بیش از حد، نوشتن‌های مهم‌تر اولویت بگیرند. تکنیک‌های batching هم با گروه‌بندی چند نوشتن در یک عملیات، سربار هر عملیات را کم می‌کنند.

راهنمای کامل ما درباره Sharding، پارتیشن‌بندی و مدیریت موج‌های نوشتن در الگوی «Scaling Writes» آمده است.

مدیریت Blobهای بزرگ

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

سرور اپلیکیشن شما اعتبارنامه‌های موقت و محدود (scoped) تولید می‌کند، یعنی presigned URL، که به کلاینت اجازه می‌دهد مستقیم در Blob Storage مثل S3 آپلود کند. دانلود هم از طریق CDN و با signed URL انجام می‌شود تا کنترل دسترسی داشته باشید. به این ترتیب سرورهای شما دیگر گلوگاه نیستند و آپلود قابل ازسرگیری، نمایش پیشرفت و توزیع جهانی هم به دست می‌آید.

چالش‌های کلیدی این الگو شامل همگام نگه داشتن state بین metadata در پایگاه داده و Blob Storage، مدیریت شکست در آپلود و مدیریت چرخه عمر فایل‌های بزرگ است. اعلان‌های رویداد (event notification) سرویس‌های ذخیره‌سازی به سازگار ماندن state برنامه کمک می‌کنند.

الگوی «Large Blobs» تکنیک‌های پیشرفته مثل presigned URL، آپلود قابل ازسرگیری و تحویل از طریق CDN را بررسی می‌کند.

فرایندهای چندمرحله‌ای

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

راه‌حل‌ها از orchestration ساده روی یک سرور تا موتورهای گردش‌کار (workflow engine) پیچیده و سیستم‌های اجرای بادوام (durable execution) را شامل می‌شود. Event Sourcing رویکرد توزیع‌شده‌ای است که در آن هر مرحله رویدادهایی منتشر می‌کند و مرحله‌های بعدی را راه می‌اندازد. سیستم‌های گردش‌کار مدرن مثل Temporal یا AWS Step Functions مدیریت state، بازیابی از شکست و منطق retry را خودکار انجام می‌دهند.

نکته اصلی گذار از مدیریت state پراکنده و مدیریت خطای دستی به تعریف اعلانی گردش‌کار است. در این حالت سیستم اجرا، اجرای دقیقاً یک‌باره (exactly-once) را تضمین می‌کند و ردپای ممیزی کاملی نگه می‌دارد.

الگوی «Multi-Step Processes» مثال‌ها و استراتژی‌های دقیق پیاده‌سازی برای workflow engine و durable execution را نشان می‌دهد.

سرویس‌های مبتنی بر مجاورت

چند سیستم، مثل طراحی Uber یا Gopuff، از شما می‌خواهند موجودیت‌ها را بر اساس مکان جستجو کنید. ایندکس‌های مکانی (spatial index) برای جستجو و بازیابی کارآمد موجودیت‌ها بر اساس نزدیکی جغرافیایی کلیدی‌اند. این سرویس‌ها اغلب به افزونه‌های پایگاه داده‌های رایج نیاز دارند، مثل PostgreSQL با افزونه PostGIS یا انواع داده مکانی Redis. راه‌حل‌های تخصصی مثل Elasticsearch با قابلیت جستجوی جغرافیایی هم گزینه دیگری است.

معماری معمولاً شامل تقسیم منطقه جغرافیایی به ناحیه‌های قابل‌مدیریت و ایندکس کردن موجودیت‌ها در همین ناحیه‌هاست. به این ترتیب سیستم می‌تواند به‌سرعت ناحیه‌های وسیعی را که موجودیت مرتبطی ندارند کنار بگذارد و فضای جستجو را به‌طور چشمگیری کوچک کند.

ایندکس‌های مکانی عالی‌اند، اما فقط وقتی لازم‌اند که باید صدها هزار یا میلیون‌ها مورد را ایندکس کنید. اگر فقط باید در نقشه‌ای با ۱۰۰۰ مورد جستجو کنید، اسکن همه آن‌ها بهتر از سربار یک ایندکس یا سرویس تخصصی است.

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

انتخاب الگو

این الگوها اغلب برای حل چالش‌های پیچیده طراحی سیستم با هم کار می‌کنند. مثلاً یک پلتفرم ویدیویی ممکن است برای آپلود ویدیو از Large Blobs، برای انکود کردن از Long-Running Tasks و برای اعلان‌های پیشرفت از Realtime Updates استفاده کند، و برای هماهنگی کل این جریان از الگوهای دیگر کمک بگیرد.

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

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