پست

کش کردن در مصاحبه‌های طراحی سیستم

مفاهیم پایه Caching شامل جایگاه کش، الگوهای معماری، سیاست‌های Eviction، مشکلات رایج و نحوه بیان آن در مصاحبه

کش کردن در مصاحبه‌های طراحی سیستم

در مصاحبه‌های طراحی سیستم، تقریباً هر بار که باید ترافیک خواندن بالا را مدیریت کنید، بحث Caching پیش می‌آید. پایگاه داده گلوگاه می‌شود، Latency کم‌کم بالا می‌رود و مصاحبه‌کننده منتظر است کلمه‌ی «Cache» را بشنود.

خواندن پروفایل کاربر از Postgres ممکن است ۵۰ میلی‌ثانیه طول بکشد، ولی خواندن از کش درون‌حافظه‌ای مثل Redis فقط ۱ میلی‌ثانیه. یعنی ۵۰ برابر بهبود در Latency. پایگاه‌های داده داده را روی دیسک نگه می‌دارند و هر Query هزینه‌ی دسترسی به دیسک را می‌پردازد. حافظه خیلی به CPU نزدیک‌تر است و از این هزینه معاف است.

کش برای سیستم‌های Scalable ضروری است. بار پایگاه داده را کم می‌کند و Latency را چشمگیر پایین می‌آورد، ولی چالش‌های تازه‌ای هم درباره‌ی Invalidation و مدیریت خرابی می‌سازد.

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

کجا کش کنیم؟

بیشتر مهندس‌ها با شنیدن کلمه‌ی Caching فوراً به Redis یا Memcached بین اپلیکیشن و پایگاه داده فکر می‌کنند. این رایج‌ترین نوع کش است و همان چیزی که مصاحبه‌کننده‌ها بیشتر از همه به آن اهمیت می‌دهند.

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

کش خارجی (External Caching)

کش خارجی یک سرویس کش مستقل است که اپلیکیشن از طریق شبکه با آن صحبت می‌کند. وقتی بیشتر مردم کلمه‌ی Caching را می‌شنوند به همین فکر می‌کنند. داده‌ی پرتکرار را در چیزی مثل Redis یا Memcached ذخیره می‌کنید تا هر بار مجبور نباشید به پایگاه داده سر بزنید.

کش‌های خارجی خوب مقیاس می‌گیرند، چون همه‌ی سرورهای اپلیکیشن می‌توانند یک کش مشترک داشته باشند. همچنین از سیاست‌های Eviction مثل LRU و انقضا با TTL پشتیبانی می‌کنند تا مصرف حافظه تحت کنترل بماند.

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

CDN (شبکه‌ی توزیع محتوا)

CDN شبکه‌ای از سرورهای توزیع‌شده‌ی جغرافیایی است که محتوا را نزدیک کاربران کش می‌کند. به‌جای اینکه هر درخواست تا سرور مبدأ (Origin) برود، CDN نسخه‌هایی از محتوا را روی Edge Serverهای سراسر جهان نگه می‌دارد.

CDNهای مدرن مثل Cloudflare، Fastly و Akamai فقط فایل‌های استاتیک را کش نمی‌کنند. می‌توانند پاسخ API های عمومی و صفحات HTML را هم کش کنند و حتی منطق Edge اجرا کنند تا پیش از رسیدن درخواست به سرورهای شما، محتوا را شخصی‌سازی یا قوانین امنیتی را اعمال کنند. با این حال رایج‌ترین و اثرگذارترین کاربرد CDN هنوز تحویل رسانه (Media Delivery) است.

نحوه‌ی کار:

  1. کاربر از اپ شما یک تصویر درخواست می‌کند.
  2. درخواست به نزدیک‌ترین Edge Server در CDN می‌رود.
  3. اگر تصویر آنجا کش شده باشد، فوراً برگردانده می‌شود.
  4. اگر نباشد، CDN آن را از سرور مبدأ می‌گیرد، ذخیره می‌کند و برمی‌گرداند.
  5. کاربران بعدی در همان منطقه تصویر را فوراً از CDN می‌گیرند.

بدون CDN هر درخواست تصویر تا سرور مبدأ می‌رود. اگر سرور شما در ویرجینیا باشد و کاربر در هند، ۲۵۰ تا ۳۰۰ میلی‌ثانیه به Latency هر درخواست اضافه می‌شود. با CDN همان تصویر از Edge Server نزدیک در ۲۰ تا ۴۰ میلی‌ثانیه سرویس داده می‌شود. این تفاوت عملکردی عظیمی است.

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

کش سمت کلاینت (Client-Side Caching)

کش سمت کلاینت داده را نزدیک درخواست‌کننده ذخیره می‌کند تا از فراخوانی‌های غیرضروری شبکه جلوگیری شود. معمولاً یعنی دستگاه کاربر، مثل مرورگر (HTTP Cache، localStorage) یا اپلیکیشن موبایل با حافظه‌ی محلی یا ذخیره‌سازی روی دستگاه.

ولی ممکن است کش داخل یک کتابخانه‌ی کلاینت هم باشد. مثلاً کلاینت‌های Redis فراداده‌ی Cluster را کش می‌کنند، مثل اینکه کدام Nodeها در Cluster هستند و کدام Slotها به آن‌ها تخصیص یافته. این‌طور کلاینت می‌تواند درخواست‌ها را مستقیم به Node درست بفرستد، بدون اینکه در هر عملیات از Cluster بپرسد.

برای کش رو به کاربر، از سمت Backend کنترل محدودی دارید. داده می‌تواند کهنه (Stale) شود و Invalidation سخت‌تر است. اپلیکیشن Strava داده‌ی دویدن شما را وقتی آفلاین هستید روی دستگاه نگه می‌دارد و بعداً همگام می‌کند. مرورگری که تصویر دانلودشده‌ی قبلی را از دیسک دوباره استفاده می‌کند هم Caching انجام می‌دهد.

کش درون‌پروسسی (In-Process Caching)

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

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

این شکل سبک کش برای تکه‌های کوچک داده‌ای مناسب است که زیاد درخواست می‌شوند، مثل:

  • مقادیر پیکربندی
  • Feature Flag ها
  • مجموعه‌داده‌های مرجع کوچک
  • Hot Keyها
  • شمارنده‌های Rate Limiting
  • مقادیر از پیش محاسبه‌شده

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

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

معماری‌های کش

همه‌ی کش‌ها یک‌جور کار نمی‌کنند. نحوه‌ی خواندن و نوشتن از/به کش، عملکرد، Consistency و پیچیدگی را عوض می‌کند. این چهار الگوی اصلی کش هستند که برای مصاحبه‌های طراحی سیستم باید بشناسید.

Cache-Aside (بارگذاری تنبل)

رایج‌ترین الگوی کش و همانی که در مصاحبه باید پیش‌فرض بگیرید.

نحوه‌ی کار:

  1. اپلیکیشن کش را بررسی می‌کند.
  2. اگر داده آنجا بود، آن را برمی‌گرداند.
  3. اگر نبود، از پایگاه داده می‌گیرد، در کش ذخیره می‌کند و برمی‌گرداند.

Cache-aside فقط وقتی لازم است داده را کش می‌کند و همین کش را لاغر نگه می‌دارد. عیبش این است که Cache Miss Latency اضافه می‌سازد.

اگر فقط یک الگوی کش را برای مصاحبه یاد بگیرید، همین را انتخاب کنید.

Write-Through

در Write-Through، اپلیکیشن فقط در کش می‌نویسد. کش بعد همگام (Synchronous) در پایگاه داده می‌نویسد و سپس به اپلیکیشن پاسخ می‌دهد. عملیات نوشتن تا به‌روز شدن هر دو، یعنی کش و پایگاه داده، کامل نمی‌شود.

در عمل این الگو به پیاده‌سازی کشی نیاز دارد که Write-Through را پشتیبانی کند، مثل کتابخانه‌ی کش با افزونه‌ی Data Store. وقتی در کش می‌نویسید، کتابخانه پیش از تأیید نوشتن، منطق نوشتن در پایگاه داده‌ی شما را صدا می‌زند. خود Redis به‌صورت بومی Write-Through را پشتیبانی نمی‌کند، پس برای پیاده‌سازی این الگو به کد اپلیکیشن یا یک فریم‌ورک نیاز دارید.

Trade-off این است که نوشتن کندتر می‌شود، چون اپلیکیشن باید منتظر به‌روز شدن کش و نوشتن در پایگاه داده بماند. Write-Through همچنین می‌تواند کش را با داده‌ای که شاید هرگز خوانده نشود پر کند (Pollution).

Write-Through هنوز مشکل Dual-write دارد. اگر به‌روزرسانی کش موفق شود ولی نوشتن در پایگاه داده شکست بخورد، یا برعکس، سیستم‌ها ناسازگار می‌شوند. به منطق Retry و مدیریت خطا نیاز دارید، یا باید بپذیرید که بدون Distributed Transaction رسیدن به Consistency کامل دشوار است.

در مصاحبه‌های طراحی سیستم، Write-Through از Cache-aside کمتر رایج است چون به زیرساخت کش تخصصی نیاز دارد و هنوز Edge Caseهای Consistency دارد.

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

Write-Behind (Write-Back)

در Write-Behind، اپلیکیشن فقط در کش می‌نویسد. کش داده را دسته‌دسته جمع می‌کند و به‌صورت Asynchronous در پس‌زمینه در پایگاه داده می‌نویسد.

این نوشتن را بسیار سریع می‌کند ولی ریسک دارد. اگر کش پیش از Flush کردن Crash کند، می‌توانید داده را از دست بدهید. برای بارهای کاری مناسب است که از دست رفتن گاه‌به‌گاه داده در آن‌ها قابل‌قبول است.

وقتی استفاده کنید که Throughput نوشتن بالا لازم دارید و Eventual Consistency قابل‌قبول است. در خط لوله‌های Analytics و متریک رایج است.

Read-Through

در Read-Through، کش مثل یک Proxy هوشمند عمل می‌کند. اپلیکیشن هرگز مستقیم با پایگاه داده صحبت نمی‌کند. هنگام Cache Miss، خود کش از پایگاه داده می‌گیرد، ذخیره می‌کند و برمی‌گرداند.

این معادل خواندنی Write-Through است. در هر دو الگو، کش واسطه‌ای است که عملیات پایگاه داده را انجام می‌دهد. Read-Through خواندن را مدیریت می‌کند و Write-Through نوشتن را. سیستم‌ها اغلب هر دو را ترکیب می‌کنند.

این الگو منطق کش را متمرکز می‌کند ولی پیچیدگی اضافه می‌کند و معمولاً به کتابخانه یا سرویس کش تخصصی نیاز دارد. در عمل از Cache-aside کمتر رایج است.

CDNها نوعی کش Read-Through هستند. وقتی CDN به Cache Miss می‌خورد، از سرور مبدأ شما می‌گیرد، نتیجه را کش می‌کند و برمی‌گرداند. اما برای کش سطح اپلیکیشن با Redis، Cache-aside خیلی رایج‌تر است.

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

سیاست‌های Eviction

کش‌ها حافظه‌ی محدودی دارند، پس وقتی پر می‌شوند به استراتژی نیاز دارند که تصمیم بگیرد کدام ورودی‌ها حذف شوند. به این استراتژی‌ها سیاست‌های Eviction می‌گویند.

LRU (کمترین استفاده‌ی اخیر): ورودی‌ای را حذف می‌کند که طولانی‌ترین مدت به آن دسترسی نشده است. ترتیب دسترسی را با ساختاری مانند لیست پیوندی ردیابی می‌کند تا کمترین استفاده‌شده‌ی اخیر در زمان ثابت حذف شود. در بسیاری از سیستم‌ها پیش‌فرض است، چون با بیشتر بارهای کاری که داده‌ی اخیراً استفاده‌شده احتمالاً دوباره استفاده می‌شود خوب سازگار است.

LFU (کمترین استفاده‌ی مکرر): ورودی‌ای را حذف می‌کند که کمترین دسترسی را داشته است. برای هر کلید شمارنده نگه می‌دارد و کلیدی را که کمترین فرکانس را دارد حذف می‌کند. بعضی پیاده‌سازی‌ها از LFU تقریبی استفاده می‌کنند تا هزینه‌ی ردیابی دقیق فرکانس را نپردازند. وقتی خوب کار می‌کند که بعضی کلیدها مدام در طول زمان محبوب بمانند، مثل ویدیوهای ترند یا پلی‌لیست‌های برتر.

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

TTL (زمان زندگی): خودش یک سیاست Eviction نیست. در عوض برای هر کلید زمان انقضا تعیین می‌کند و ورودی‌های قدیمی‌شده را حذف می‌کند. اغلب با LRU یا LFU ترکیب می‌شود تا بین تازگی و مصرف حافظه تعادل ایجاد کند. TTL وقتی ضروری است که داده باید سرانجام تازه شود، مثل پاسخ API یا توکن‌های نشست.

مشکلات رایج Caching

کش سیستم را سریع‌تر می‌کند ولی حالت‌های شکست جدیدی هم می‌آورد. این مشکلات در سیستم‌های واقعی با مقیاس بالا بروز می‌کنند و مصاحبه‌کننده‌ها اغلب با آن‌ها می‌سنجند که Trade-offهای کش را می‌فهمید، نه فقط مزایایش را. اگر در مصاحبه کش را مطرح می‌کنید، باید نشان دهید می‌توانید این Edge Caseها را هم مدیریت کنید.

Cache Stampede (Thundering Herd)

Cache Stampede وقتی رخ می‌دهد که یک ورودی محبوب کش منقضی شود و درخواست‌های زیادی هم‌زمان بخواهند آن را بازسازی کنند. بازه‌ی کوتاهی هست، حتی اگر فقط یک ثانیه، که همه‌ی درخواست‌ها به کش می‌خورند و مستقیم به پایگاه داده می‌روند. به‌جای یک Query ناگهان صدها یا هزاران Query دارید که می‌تواند پایگاه داده را از پا درآورد.

مثلاً فرض کنید سیستم شما Feed صفحه‌ی اصلی را با TTL برابر ۶۰ ثانیه کش می‌کند. وقتی کش دقیقاً در ساعت 12:01:00 منقضی می‌شود، همه‌ی درخواست‌های آن لحظه Miss می‌شوند و پایگاه داده را Query می‌کنند. اگر ترافیک زیاد باشد، این جهش می‌تواند پایگاه داده را بترکاند و شکست آبشاری (Cascading Failure) ایجاد کند.

راه مدیریت:

  • Request Coalescing (Single Flight): فقط یک درخواست اجازه دارد کش را بازسازی کند و بقیه منتظر نتیجه می‌مانند. مؤثرترین راه‌حل همین است.
  • Cache Warming: کلیدهای محبوب را پیش از انقضا فعالانه تازه کنید. فقط وقتی کمک می‌کند که از انقضای مبتنی بر TTL استفاده می‌کنید. اگر به‌جای آن کش را هنگام نوشتن Invalidate می‌کنید، Warming از Stampede جلوگیری نمی‌کند.

Cache Consistency

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

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

راه‌حل کاملی وجود ندارد. استراتژی را بر اساس اینکه داده چقدر باید تازه باشد انتخاب می‌کنید.

راه مدیریت:

  • Invalidation هنگام نوشتن: بعد از به‌روزرسانی پایگاه داده، ورودی کش را حذف کنید تا با داده‌ی تازه دوباره پر شود.
  • TTL کوتاه برای تحمل کهنگی: اگر Eventual Consistency قابل‌قبول است، بگذارید داده‌ی کمی کهنه موقتاً بماند.
  • پذیرش Eventual Consistency: برای Feedها، متریک‌ها و Analytics معمولاً تأخیر کوتاه مشکلی ندارد.

Hot Keys

Hot Key ورودی کشی است که در مقایسه با بقیه ترافیک بسیار زیادی می‌گیرد. حتی اگر Cache Hit Rate بالا باشد، یک Hot Key می‌تواند یک Node کش یا یک Shard از Redis را زیر بار ببرد و گلوگاه شود.

مثلاً اگر Twitter می‌سازید و همه در حال دیدن پروفایل Taylor Swift هستند، کلید کش داده‌ی کاربر او (user:taylorswift) ممکن است میلیون‌ها درخواست در ثانیه بگیرد. همین یک کلید می‌تواند یک Node از Redis را از پا درآورد، حتی اگر همه‌چیز «درست» کار کند.

راه مدیریت:

  • Replicate کردن Hot Keyها: همان مقدار را روی چند Node کش ذخیره کنید و خواندن‌ها را بین آن‌ها Load Balance کنید.
  • افزودن کش محلی Fallback: مقادیر فوق‌العاده داغ را درون پروسس نگه دارید تا به Redis فشار نیاید.
  • اعمال Rate Limiting: الگوهای ترافیکی سوءاستفاده‌گرانه روی کلیدهای مشخص را کند کنید.

الگو، Scaling Reads: سناریوهای Hot Key فقط مختص کش‌ها نیستند. در سیستم‌های توزیع‌شده مشکل رایجی است. وقتی میلیون‌ها کاربر هم‌زمان یک محتوای وایرال را درخواست می‌کنند، مفروضات سنتی Caching از کار می‌افتند.

Caching در مصاحبه‌های طراحی سیستم

Caching تقریباً در هر مصاحبه‌ی طراحی سیستم مطرح می‌شود، پس مهم است بدانید کی آن را مطرح کنید و چطور سیستماتیک پیش ببرید.

کی کش را مطرح کنیم؟

مستقیم سراغ کش نروید. اول باید دلیل ضرورتش را مشخص کنید. وقتی یکی از این مشکلات را شناسایی کردید کش را مطرح کنید:

  • بار کاری سنگین خواندنی: «ما ۱۰ میلیون کاربر فعال روزانه داریم که هرکدام ۲۰ درخواست در روز می‌دهند. یعنی ۲۰۰ میلیون خواندن به پایگاه داده می‌خورد. حتی با Index هم ۲۰ تا ۵۰ میلی‌ثانیه برای هر Query داریم. کش آن را به زیر ۲ میلی‌ثانیه می‌رساند و بخش بزرگی از بار را از پایگاه داده برمی‌دارد.»
  • Queryهای گران: «محاسبه‌ی Feed شخصی‌سازی‌شده‌ی کاربر نیاز به Join روی پست‌ها، فالوورها و لایک‌ها در چند جدول دارد. این Query ۲۰۰ میلی‌ثانیه طول می‌کشد. می‌توانیم Feed محاسبه‌شده را ۶۰ ثانیه کش کنیم و در ۱ میلی‌ثانیه از Redis سرویس بدهیم.»
  • CPU بالای پایگاه داده: «CPU پایگاه داده ما در ساعات اوج فقط برای سرویس‌دهی خواندن به ۸۰٪ می‌رسد. همان Queryها مدام تکرار می‌شوند. کش کردن Queryهای داغ بار پایگاه داده را ۷۰ تا ۸۰٪ کم می‌کند.»
  • نیازمندی‌های Latency: «برای API به زمان پاسخ زیر ۱۰ میلی‌ثانیه نیاز داریم. Queryهای پایگاه داده ۳۰ تا ۵۰ میلی‌ثانیه طول می‌کشند. باید کش کنیم.»

الگو ساده است: مشکل عملکرد را شناسایی کنید، با اعداد تقریبی کمّی‌اش کنید و توضیح دهید کش چطور آن را حل می‌کند. برای تخمین Latency معقول پایگاه داده و کش می‌توانید از «اعداد مهمی که باید بدانید» (Numbers to Know) استفاده کنید.

چطور کش را معرفی کنیم؟

بعد از اثبات نیاز به کش، استراتژی را سیستماتیک پیش ببرید:

۱) گلوگاه را شناسایی کنید. با اشاره به مشکل مشخصی شروع کنید که کش حل می‌کند. بار پایگاه داده؟ Latency Query؟ محاسبات گران؟ دقیق بگویید چه چیزی کند است و چرا.

«Queryهای پروفایل کاربر در ساعات اوج ۵۰۰ بار در ثانیه به پایگاه داده می‌خورند. هر Query ۳۰ میلی‌ثانیه طول می‌کشد. این گلوگاه ماست.»

۲) تصمیم بگیرید چه چیزی کش شود. همه‌چیز را کش نکنید. روی داده‌ای تمرکز کنید که زیاد خوانده می‌شود، کم تغییر می‌کند و دریافت یا محاسبه‌اش گران است.

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

به کلیدهای کش فکر کنید. داده‌ی کش‌شده را چطور پیدا می‌کنید؟ برای پروفایل کاربر کلید می‌تواند user:123:profile باشد و برای پست‌های ترند trending:posts:global.

۳) معماری کش را انتخاب کنید. الگوی کشی را انتخاب کنید که با نیازهای Consistency شما جور باشد. Write-Through وقتی منطقی است که Strong Consistency لازم دارید. Write-Behind برای نوشتن‌های پرحجم مناسب است که ریسکی را تحمل می‌کنند.

«از Cache-aside استفاده می‌کنم. هنگام خواندن اول Redis را بررسی می‌کنیم. اگر بود برمی‌گردانیم. اگر نبود، پایگاه داده را Query می‌کنیم، نتیجه را در Redis ذخیره و برمی‌گردانیم.»

اگر با محتوای استاتیک مثل تصویر یا ویدیو سروکار دارید، CDN را مطرح کنید. اگر Hot Keyهای بسیار داغی دارید که زیر فشارند، کش درون‌پروسسی را به‌عنوان لایه‌ی بهینه‌سازی مطرح کنید.

۴) سیاست Eviction را تعیین کنید. توضیح دهید اندازه‌ی کش را چطور مدیریت می‌کنید. LRU پاسخ پیش‌فرض امن است. TTL برای جلوگیری از داده‌ی کهنه ضروری است.

«از Eviction با LRU در Redis استفاده می‌کنیم و TTL برابر ۱۰ دقیقه روی پروفایل کاربر می‌گذاریم. این کار مانع رشد بی‌حد کش می‌شود و در عین حال مطمئن می‌شود پروفایل‌ها زیاد کهنه نمی‌شوند. اگر کاربر پروفایلش را به‌روز کند، ورودی کش را فوراً Invalidate می‌کنیم.»

۵) به نقاط ضعف بپردازید. کش پیچیدگی می‌آورد. نشان دهید به Trade-offها فکر کرده‌اید.

  • Cache Invalidation: داده‌ی کش‌شده را چطور تازه نگه می‌دارید؟ هنگام نوشتن Invalidate می‌کنید، به TTL تکیه می‌کنید، یا Eventual Consistency را می‌پذیرید؟ مثال: «وقتی کاربر پروفایلش را به‌روز می‌کند، ورودی کش را حذف می‌کنیم تا خواندن بعدی داده‌ی تازه را از پایگاه داده بگیرد.»
  • خرابی کش: اگر Redis از کار بیفتد چه می‌شود؟ آیا پایگاه داده زیر جهش ناگهانی ترافیک له می‌شود؟ مثال: «اگر Redis در دسترس نباشد، درخواست‌ها به پایگاه داده Fallback می‌کنند. Circuit Breaker اضافه می‌کنیم تا با Stampede پایگاه داده را غرق نکنیم. همچنین می‌توانیم یک کش درون‌پروسسی کوچک به‌عنوان لایه‌ی آخرین راه نگه داریم.»
  • Thundering Herd: وقتی یک ورودی محبوب کش منقضی شود و ۱۰۰۰ درخواست هم‌زمان بخواهند آن را دوباره بگیرند چه می‌شود؟ مثال: «برای کلیدهای بسیار محبوب می‌توانیم از Probabilistic Early Expiration یا Request Coalescing استفاده کنیم تا فقط یک درخواست از پایگاه داده بگیرد و بقیه منتظر همان نتیجه بمانند.»

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

نتیجه‌گیری

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

Trade-off اصلی ساده است. کش‌ها خواندن را سریع‌تر می‌کنند و بار هر چیزی را که پشتشان است کم می‌کنند، ولی پیچیدگی کهنگی و Invalidation می‌آورند. داده‌ی کش‌شده می‌تواند با پایگاه داده ناهمگام شود. خرابی کش اگر آماده نباشید می‌تواند پایگاه داده را له کند. Hot Keyها حتی در کش‌های توزیع‌شده هم می‌توانند گلوگاه بسازند.

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

از همه مهم‌تر، همه‌چیز را کش نکنید. نشان دهید می‌فهمید کی کش ارزش پیچیدگی‌اش را دارد و کی یک پایگاه داده‌ی خوب Index شده کافی است.