پست

بررسی عمیق قضیه CAP برای مصاحبه‌های طراحی سیستم

تسلط بر مصالحه اصلی بین سازگاری و دسترس‌پذیری در سیستم‌های توزیع‌شده، همراه با مثال‌های عملی، نحوه بحث درباره CAP در مصاحبه و مدل‌های مختلف سازگاری

بررسی عمیق قضیه CAP برای مصاحبه‌های طراحی سیستم

قضیه CAP

در این فصل با مصالحه اصلی بین سازگاری (Consistency) و دسترس‌پذیری (Availability) در سیستم‌های توزیع‌شده آشنا می‌شویم.

قضیه CAP معمولاً برای کاندیداها نقطه سردرگمی است، اما برای نحوه برخورد شما با طراحی در مصاحبه اهمیت اساسی دارد.

در این فصل توضیح می‌دهیم قضیه CAP چیست، چطور کار می‌کند و هنگام در نظر گرفتن آن در مرحله الزامات غیرعملکردی (Non-Functional Requirements) مصاحبه طراحی سیستم، چه مصالحه‌های عملی‌ای باید انجام دهید.

قضیه CAP چیست؟

قضیه CAP در هسته خود می‌گوید در یک سیستم توزیع‌شده فقط می‌توانید دو مورد از این سه ویژگی را داشته باشید.

  • سازگاری (Consistency): همه گره‌ها هم‌زمان داده یکسانی را می‌بینند. وقتی نوشتنی روی یک گره انجام می‌شود، همه خواندن‌های بعدی، از هر گرهای که باشند، همان مقدار به‌روزشده را برمی‌گردانند.
  • دسترس‌پذیری (Availability): هر درخواستی که به یک گره سالم برسد پاسخ می‌گیرد، بدون این‌که تضمینی باشد که پاسخ شامل جدیدترین نسخه داده است.
  • تحمل پارتیشن (Partition Tolerance): سیستم با وجود از دست رفتن پیام‌های دلخواه یا خرابی بخشی از سیستم (یعنی پارتیشن‌های شبکه بین گره‌ها) به کار خود ادامه می‌دهد.

توجه کنید که «سازگاری» در قضیه CAP با سازگاری‌ای که پایگاه‌های داده ACID تضمین می‌کنند کاملاً فرق دارد. می‌دانم گیج‌کننده است.

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

یعنی در عمل قضیه CAP به یک انتخاب خلاصه می‌شود: وقتی پارتیشن شبکه رخ می‌دهد، سازگاری را در اولویت می‌گذارید یا دسترس‌پذیری را؟ برای فهمیدن معنای این انتخاب، یک مثال عملی را بررسی می‌کنیم.

فهم CAP با یک مثال

تصور کنید وب‌سایتی با دو سرور اجرا می‌کنید، یکی در آمریکا و یکی در اروپا. وقتی کاربری پروفایل عمومی‌اش را به‌روز می‌کند (مثلاً نام نمایشی)، این اتفاق می‌افتد.

  1. کاربر A به نزدیک‌ترین سرور خودش (آمریکا) وصل می‌شود و نامش را به‌روز می‌کند.
  2. این به‌روزرسانی روی سرور اروپا replicate می‌شود.
  3. وقتی کاربر B در اروپا پروفایل کاربر A را می‌بیند، نام به‌روزشده را می‌بیند.

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

  • گزینه A: خطا برگردانیم، چون نمی‌توانیم تضمین کنیم داده به‌روز است (انتخاب سازگاری).
  • گزینه B: داده‌ای را نشان دهیم که ممکن است کهنه باشد (انتخاب دسترس‌پذیری).

اینجاست که قضیه CAP عملاً وارد کار می‌شود. باید بین سازگاری و دسترس‌پذیری یکی را انتخاب کنیم.

در این مورد پاسخ نسبتاً روشن است. ترجیح می‌دهیم به کاربری در اروپا نام قدیمی کاربر A را نشان دهیم تا این‌که خطا ببیند. دیدن نام کهنه بهتر از نداشتن هیچ نامی است.

چند نمونه واقعی دیگر از این انتخاب را ببینیم.

چه زمانی سازگاری را انتخاب کنیم

بعضی سیستم‌ها کاملاً به سازگاری نیاز دارند، حتی به قیمت دسترس‌پذیری.

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

چه زمانی دسترس‌پذیری را انتخاب کنیم

بیشتر سیستم‌ها می‌توانند مقداری ناسازگاری را تحمل کنند و باید دسترس‌پذیری را در اولویت بگذارند. در این موارد سازگاری نهایی (Eventual Consistency) قابل‌قبول است. یعنی سیستم در نهایت سازگار می‌شود، اما ممکن است چند ثانیه یا چند دقیقه طول بکشد.

  • شبکه‌های اجتماعی: اگر کاربر A عکس پروفایلش را عوض کند، مشکلی نیست که کاربر B چند دقیقه عکس قدیمی را ببیند.
  • پلتفرم‌های محتوا (مثل Netflix): اگر کسی توضیحات یک فیلم را به‌روز کند، نشان دادن موقت توضیحات قدیمی به بعضی کاربران فاجعه نیست.
  • سایت‌های نقد و بررسی (مثل Yelp): اگر رستورانی ساعت کاری‌اش را به‌روز کند، نمایش مختصر اطلاعات کمی قدیمی بهتر از نمایش ندادن اطلاعات است.

سوال کلیدی که باید از خودتان بپرسید این است: «اگر کاربران برای مدت کوتاهی داده ناسازگار ببینند، فاجعه می‌شود؟» اگر جواب مثبت است، سازگاری را انتخاب کنید. اگر نه، دسترس‌پذیری را.

قضیه CAP در مصاحبه

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

در مصاحبه طراحی سیستم معمولاً این کارها را می‌کنید.

  1. روی الزامات عملکردی (قابلیت‌ها) به توافق می‌رسید.
  2. الزامات غیرعملکردی (کیفیت‌های سیستم) را تعریف می‌کنید.

هنگام بحث درباره الزامات غیرعملکردی، قضیه CAP باید نقطه شروع شما باشد. باید این سوال مهم را بپرسید: «این سیستم باید سازگاری را در اولویت بگذارد یا دسترس‌پذیری را؟»

اگر سازگاری را در اولویت بگذارید

در این حالت طراحی شما ممکن است این موارد را شامل شود.

  • تراکنش‌های توزیع‌شده: مطمئن شدن از هماهنگ بودن چند ذخیره‌گاه داده (مثل کش و پایگاه داده) با پروتکل‌های two-phase commit. این کار پیچیدگی اضافه می‌کند اما سازگاری را در همه گره‌ها تضمین می‌کند. یعنی کاربران احتمالاً تأخیر بیشتری را تجربه می‌کنند، چون سیستم مطمئن می‌شود داده در همه گره‌ها سازگار است.
  • راه‌حل تک‌گره‌ای (single-node): استفاده از یک نمونه پایگاه داده برای پرهیز کامل از مشکل انتشار داده. این کار مقیاس‌پذیری را محدود می‌کند، اما با داشتن یک منبع حقیقت (source of truth) چالش‌های سازگاری را حذف می‌کند.

انتخاب‌های فناوری در این حالت:

  • پایگاه‌های داده رابطه‌ای سنتی (PostgreSQL، MySQL)
  • Google Spanner
  • DynamoDB (در حالت Strong Consistency)

اگر دسترس‌پذیری را در اولویت بگذارید

در این حالت طراحی شما می‌تواند این موارد را شامل شود.

  • چند replica: مقیاس دادن با replicaهای خواندنی اضافی با replication ناهمزمان، که اجازه می‌دهد خواندن از هر replica انجام شود، حتی اگر کمی عقب باشد. این کار کارایی خواندن و دسترس‌پذیری را به قیمت احتمال کهنگی داده بهتر می‌کند.
  • Change Data Capture (CDC): استفاده از CDC برای ردیابی تغییرات پایگاه داده اصلی و انتشار ناهمزمان آن‌ها به replicaها، کش و سیستم‌های دیگر. این روش اجازه می‌دهد سیستم اصلی در دسترس بماند و به‌روزرسانی‌ها در نهایت در سیستم پخش شوند.

انتخاب‌های فناوری در این حالت:

  • Cassandra
  • DynamoDB (در پیکربندی چندریجنی با دسترس‌پذیری بالا)
  • Redis Cluster

بیشتر پایگاه‌های داده توزیع‌شده مدرن گزینه‌های پیکربندی هم برای سازگاری و هم برای دسترس‌پذیری دارند. نکته کلیدی این است که بدانید برای کاربرد خودتان کدام را انتخاب کنید.

ملاحظات پیشرفته CAP

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

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

قابلیت‌های مختلف، نیازهای مختلف

سیستم‌های واقعی اغلب هم دسترس‌پذیری می‌خواهند و هم سازگاری، فقط برای قابلیت‌های متفاوت. دو مثال می‌بینیم.

مثال ۱، Ticketmaster: Ticketmaster برای قابلیت‌های مختلف در یک سیستم به مدل‌های سازگاری متفاوت نیاز دارد.

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

در مصاحبه می‌توانید بگویید: «برای این سیستم فروش بلیت، برای تراکنش‌های رزرو سازگاری را در اولویت می‌گذارم، اما برای زمانی که کاربران در حال مرور و مشاهده رویدادها هستند، دسترس‌پذیری را بهینه می‌کنم.»

مثال ۲، Tinder: Tinder هم الزامات متفاوتی دارد.

  • Match شدن: به سازگاری نیاز دارد. اگر دو کاربر تقریباً هم‌زمان یکدیگر را به راست بکشند، هر دو باید فوراً match را ببینند.
  • مشاهده پروفایل کاربران: می‌تواند دسترس‌پذیری را در اولویت بگذارد. اگر کاربری تازه عکسش را عوض کرده باشد، دیدن عکس پروفایل کمی قدیمی قابل‌قبول است.

در مصاحبه می‌توانید بگویید: «برای این اپلیکیشن دوست‌یابی، برای match شدن سازگاری را در اولویت می‌گذارم، اما وقتی کاربران در حال مشاهده پروفایل‌ها هستند، دسترس‌پذیری را بهینه می‌کنم.»

سطوح مختلف سازگاری

وقتی درباره سازگاری در قضیه CAP صحبت می‌شود، معمولاً منظور مردم سازگاری قوی (Strong Consistency) است، یعنی همه خواندن‌ها آخرین نوشتن را منعکس می‌کنند. اما شناخت طیف مدل‌های سازگاری کمک می‌کند تصمیم‌های طراحی دقیق‌تری بگیرید.

  • سازگاری قوی (Strong Consistency): همه خواندن‌ها جدیدترین نوشتن را منعکس می‌کنند. از نظر کارایی گران‌ترین مدل سازگاری است، اما برای سیستم‌هایی که دقت مطلق می‌خواهند، مثل موجودی حساب بانکی، ضروری است. تا اینجا درباره همین مدل بحث کردیم.
  • سازگاری علی (Causal Consistency): رویدادهای مرتبط برای همه کاربران با یک ترتیب دیده می‌شوند. این مدل ترتیب منطقی اقدام‌های وابسته را تضمین می‌کند. مثلاً مطمئن می‌شود کامنت‌های یک پست بعد از خود پست ظاهر شوند.
  • سازگاری خواندن نوشته‌های خود (Read-Your-Writes Consistency): کاربران همیشه به‌روزرسانی‌های خودشان را فوراً می‌بینند، هرچند کاربران دیگر ممکن است نسخه‌های قدیمی‌تر را ببینند. این مدل معمولاً در پلتفرم‌های شبکه اجتماعی استفاده می‌شود که کاربران انتظار دارند به‌روزرسانی پروفایل‌شان را فوراً ببینند.
  • سازگاری نهایی (Eventual Consistency): سیستم با گذشت زمان سازگار می‌شود، اما ممکن است موقتاً ناسازگار باشد. این آسان‌گیرانه‌ترین شکل سازگاری است و اغلب در سیستم‌هایی مثل DNS استفاده می‌شود که ناسازگاری موقت قابل‌قبول است. این رفتار پیش‌فرض بیشتر پایگاه‌های داده توزیع‌شده است و وقتی دسترس‌پذیری را در اولویت می‌گذاریم، به‌طور ضمنی همین را انتخاب می‌کنیم.

جمع‌بندی

قضیه CAP مهم است. زمینه نحوه برخورد شما با طراحی در مصاحبه را فراهم می‌کند و نباید نادیده گرفته شود.

اما لازم نیست پیچیده باشد. فقط از خودتان بپرسید: «آیا هر خواندن باید آخرین نوشتن را ببیند؟» اگر جواب مثبت است، باید سازگاری را در اولویت بگذارید. اگر جواب منفی است، می‌توانید دسترس‌پذیری را در اولویت بگذارید.