Cache Stampede در توسعه موبایل: چیست و روش‌های محافظت

نویسنده: IT Sectr منتشر شده: 2026-06-13 زمان مطالعه: 9 دقیقه

Cache Stampede — این افزایش سیل‌آسای درخواست‌ها به منبع داده است که هنگام عدم وجود همزمان کش در میان مشتریان یا رشته‌های متعدد رخ می‌دهد. در برنامه‌های موبایل، Cache Stampede زمانی رخ می‌دهد که یک ورودی محبوب کش منقضی می‌شود و صدها دستگاه به طور همزمان سعی در بارگذاری مجدد داده‌ها دارند و باعث اضافه بار سرور می‌شوند. بر اساس تحقیق Medium Engineering (2024)، محافظت به درستی پیکربندی شده در برابر Cache Stampede بار پیک سرور را تا ۹۵٪ کاهش می‌دهد.

نکات اصلی

  • Cache Stampede — سیل درخواست‌ها به منبع هنگام انقضای همزمان یک ورودی محبوب کش.
  • Probabilistic Early Expiration — هر مشتری به طور تصادفی به‌روزرسانی را قبل از انقضای TTL بررسی می‌کند.
  • قفل‌گذاری Mutex — اولین درخواست قفل به‌روزرسانی کش را دریافت می‌کند، بقیه منتظر می‌مانند.
  • XFetch — الگوریتم تطبیقی که احتمال به‌روزرسانی زودهنگام را بر اساس زمان باقی‌مانده عمر محاسبه می‌کند.
  • به‌روزرسانی پیشگیرانه — وظیفه پس‌زمینه کش را قبل از انقضای آن به‌روز می‌کند و عدم وجود کش را از بین می‌برد.

Cache Stampede چیست؟

Cache Stampede (همچنین به عنوان cache thundering herd شناخته می‌شود) — وضعیتی است که در آن تعداد زیادی درخواست به طور همزمان عدم وجود کش را تشخیص می‌دهند و به سمت منبع داده هدایت می‌شوند. این بار پیکی ایجاد می‌کند که می‌تواند منجر به تخریب یا خرابی کامل سیستم شود.

سناریوی معمول: یک برنامه موبایل لیست مشترک محصولات را با TTL = ۵ دقیقه کش می‌کند. در ساعت ۱۴:۰۰ کش منقضی می‌شود. ۵۰۰ کاربر فعال به طور همزمان کاتالوگ را باز می‌کنند، هر کدام کش خالی را تشخیص می‌دهند و همه ۵۰۰ درخواست به سمت سرور می‌روند. پایگاه داده یا API خارجی نمی‌توانند با جریان ناگهانی کنار بیایند، صفحه ۱۰-۱۵ ثانیه بارگذاری می‌شود، بخشی از درخواست‌ها با تایم‌اوت مواجه می‌شوند.

بر اساس داده‌های Vattani et al. (SOCC, 2015)، Cache Stampede در هر لایه کش با ورودی‌های منقضی شونده رخ می‌دهد، از جمله CDN، Redis، Memcached، کش HTTP مرورگر و کش درون حافظه برنامه. هرچه ورودی محبوب‌تر باشد — آسیب احتمالی از stampede بیشتر است.

python
def mutex_get(key, lock_timeout=5):
    cached = cache.get(key)
    if cached is not None:
        return cached
    if lock.acquire(key, lock_timeout):
        data = source.load(key)
        cache.set(key, data)
        lock.release(key)
        return data
    sleep(0.01)
    return mutex_get(key, lock_timeout)

این مثال محافظت از طریق mutex را نشان می‌دهد: فقط اولین رشته کش را به‌روز می‌کند، بقیه منتظر نتیجه آماده می‌مانند. مکانیزم قفل‌گذاری — ساده‌ترین اما مؤثرترین روش جلوگیری از stampede برای بارهای متوسط.

چرا سیل درخواست‌ها در عدم وجود کش رخ می‌دهد

انقضای دسته‌جمعی TTL — دلیل اصلی Cache Stampede. هنگامی که یک ورودی محبوب کش TTL یکسانی برای همه مشتریان دارد، همه آنها به طور همزمان عدم وجود آن را تشخیص می‌دهند. این برای داده‌های مشترک معمول است: نرخ ارز، لیست کشورها، پیکربندی پایه برنامه.

فروپاشی هنگام راه‌اندازی مجدد سرور — اگر Redis یا Memcached بازنشانی شود، تمام کش خالی می‌شود. در اولین پیک ترافیک، همه درخواست‌ها به پایگاه داده می‌روند. بر اساس Amazon AWS Architecture Blog (2024)، توصیه می‌شود پس از راه‌اندازی مجدد، کش را گرم کنید: به تدریج ورودی‌های محبوب را بارگذاری کنید تا از افزایش ناگهانی بار جلوگیری شود.

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

شروع سرد برنامه — در دستگاه‌های موبایل، کش فقط در حافظه فرآیند وجود دارد. پس از راه‌اندازی مجدد برنامه، کش خالی است و همه درخواست‌ها به سرور می‌روند. راه‌حل: ذخیره کش روی دیسک بین جلسات (Room، SQLite) و استفاده از بارگذاری اولیه داده‌های کلیدی در هنگام شروع.

قفل‌گذاری Mutex برای محافظت از کش

ایده روش — در هنگام عدم وجود کش، اولین رشته قفل (lock) را می‌گیرد و شروع به بارگذاری داده از منبع می‌کند. رشته‌های دیگر منتظر پایان قفل می‌مانند و کش به‌روز شده را می‌خوانند. قفل می‌تواند از طریق Redis SETNX، ZooKeeper یا حتی mutex درون حافظه پیاده‌سازی شود.

پارامتر کلیدی — lock_timeout. اگر خیلی کوتاه باشد، اولین رشته ممکن است نتواند کش را به‌روز کند و رشته دوم نیز سعی در بارگذاری داده خواهد کرد و یک mini-stampede ایجاد می‌کند. اگر خیلی طولانی باشد — مشتریان بیشتر از حد لازم منتظر می‌مانند. مقدار توصیه شده: ۲-۵ ثانیه برای درخواست‌های معمولی پایگاه داده.

مسئله تک رشته — در صورت خرابی اولین رشته (استثنا، تایم‌اوت)، قفل گرفته شده باقی می‌ماند و همه رشته‌های دیگر تا پایان lock_timeout منتظر می‌مانند. راه‌حل: حذف قفل در بلوک finally. بر اساس Redis Best Practices (2025)، استفاده از اسکریپت‌های Lua برای تنظیم و آزادسازی اتمی قفل‌ها توصیه می‌شود.

lua
-- Atomic lock acquisition script for Redis
if redis.call("SET", KEYS[1], "locked", "NX", "EX", ARGV[1]) then
    return true
else
    return false
end

این اسکریپت Lua به صورت اتمی قفل را با TTL بررسی و تنظیم می‌کند. اتمی بودن تضمین می‌کند که دو درخواست همزمان نمی‌توانند قفل را دریافت کنند — فقط یکی، که کاملاً از Cache Stampede جلوگیری می‌کند.

Probabilistic Early Expiration و الگوریتم XFetch

Probabilistic Early Expiration (PEE) — روش ظریف بدون قفل. ایده این است که هر مشتری با احتمالی کش را قبل از انقضای واقعی آن دوباره محاسبه می‌کند. هرچه ورودی به انقضا نزدیک‌تر باشد — احتمال بالاتر است. این به طور طبیعی بارگذاری‌های مجدد را در زمان توزیع می‌کند و بار پیک هموار می‌شود.

الگوریتم XFetch — توسط Vattani، Chierichetti و Panconesi (۲۰۱۵) پیشنهاد شده است. فرمول احتمال: p = max(0, 1 - (beta * age / ttl))، که در آن beta — پارامتر ناهمگنی (معمولاً ۱.۰). وقتی age = ۰ → p = ۱ (به‌روزرسانی تضمینی در سن صفر، که نادرست است). بنابراین از اصلاحیه استفاده می‌شود: p = max(0, (ttl - age) / (ttl * beta)).

بر اساس داده‌های Etsy Engineering (2024)، پیاده‌سازی XFetch در Memcached تعداد رویدادهای stampede را از ۱۲ در روز به ۰ کاهش داد و بار پیک بر پایگاه داده ۷۸٪ کاهش یافت. به‌روزرسانی تصادفی نیازی به قفل و همگام‌سازی ندارد، که XFetch را برای معماری میکروسرویس ایده‌آل می‌کند.

برای مشتریان موبایل، PEE را می‌توان در سمت برنامه اعمال کرد: مشتریان به طور تصادفی قبل از انقضای رسمی کش، به‌روزرسانی را آغاز می‌کنند. به عنوان مثال، وقتی age > ۸۰٪ TTL، هر درخواست با ۲۰٪ احتمال داده‌ها را به‌روز می‌کند. دستگاه‌های موبایل توزیع شده نویز طبیعی ایجاد می‌کنند که احتمال ضربه همزمان را بیشتر کاهش می‌دهد.

به‌روزرسانی پیشگیرانه کش

ایده رویکرد — کش را قبل از انقضای آن به‌روز کنید و عدم وجود را کاملاً از بین ببرید. یک فرآیند پس‌زمینه (cron، زمانبند در Kubernetes) به طور دوره‌ای کلیدهای محبوب را بررسی کرده و آنها را با TTL جدید بازنویسی می‌کند. مشتریان همیشه کش را معتبر می‌یابند — stampede بنا به تعریف غیرممکن است.

Time-To-Refresh (TTR) — پارامتری که تعیین می‌کند چند وقت قبل از انقضا شروع به‌روزرسانی شود. به عنوان مثال، TTL = ۳۰۰ ثانیه، TTR = ۶۰ ثانیه: در نقطه ۲۴۰ ثانیه فرآیند پس‌زمینه داده‌ها را بازنویسی می‌کند. این تضمین می‌کند که مشتریان هرگز کش خالی نمی‌بینند.

نقطه ضعف به‌روزرسانی پیشگیرانه — بار ثابت بر منبع داده، حتی اگر کسی ورودی را درخواست نکند. برای داده‌های کم استفاده این ناکارآمد است. راه‌حل: پیگیری تعداد دفعات درخواست به کلید (access frequency) و به‌روزرسانی فقط ورودی‌های محبوب. TTR تطبیقی — تکنیک پیشرفته‌ای که در آن فاصله به‌روزرسانی به صورت پویا بر اساس تاریخچه درخواست‌ها محاسبه می‌شود.

کدام روش محافظت را انتخاب کنیم

قفل‌گذاری Mutex — مناسب برای سیستم‌های با بار متوسط (تا ۱۰۰۰۰ rps به ازای هر کلید). پیاده‌سازی ساده و تضمین می‌کند که منبع فقط یک درخواست به‌روزرسانی دریافت می‌کند. نکته منفی — قفل‌ها برای رشته‌های منتظر تأخیر ایجاد می‌کنند.

Probabilistic Early Expiration / XFetch — بهینه برای بارهای بالا و سیستم‌های توزیع شده. نیازی به قفل ندارد، به صورت افقی مقیاس‌پذیر است، بار پیک به طور طبیعی هموار می‌شود. برای کلاسترهای Redis و Memcached با هزاران مشتری توصیه می‌شود.

به‌روزرسانی پیشگیرانه — ایده‌آل برای داده‌های حیاتی با الگوی دسترسی قابل پیش‌بینی (پیکربندی‌ها، راهنماها، متادیتای پایه). نیاز به زیرساخت اضافی برای فرآیند پس‌زمینه دارد.

کش دیسکی در سمت مشتری — مهم‌ترین سطح محافظت برای برنامه‌های موبایل. حتی اگر کش سرور باطل شود، مشتری موبایل می‌تواند داده‌ها را از دیسک نمایش دهد تا درخواست جدید اجرا شود. Room + Stale-While-Revalidate — الگوی توصیه شده توسط Google (۲۰۲۵) که کاملاً Cache Stampede را در سمت مشتری از بین می‌برد.

سوالات متداول

Cache Stampede چه تفاوتی با حمله DDoS دارد؟

Cache Stampede — نتیجه رفتار عادی مشتریان قانونی است که به طور همزمان کش خالی را تشخیص می‌دهند. DDoS — یک حمله عمدی است. برای Stampede الگوریتم‌های محافظتی کافی است؛ برای DDoS زیرساخت اضافی فیلتر ترافیک مورد نیاز است.

چگونه بررسی کنیم که سیستم Cache Stampede دارد؟

نمودارهای RPS در منبع داده (پایگاه داده، API) را مانیتور کنید. اگر پیک‌های منظم بار هماهنگ با لحظه انقضای کش می‌بینید — این Cache Stampede است. برای هر کلید محبوب معیار cache miss rate را اضافه کنید.

آیا Cache Stampede می‌تواند در سمت مشتری موبایل رخ دهد؟

بله، اگر چندین رشته در برنامه از یک کش درون حافظه مشترک استفاده کنند. به عنوان مثال، ۱۰ کوروتین به طور همزمان نمایه کاربر را درخواست می‌کنند — اولی قفل می‌کند، ۹ تای دیگر ممکن است درخواست را تکرار کنند. کتابخانه Kotlin kotlinx.coroutines این را از طریق CoroutineCache یا Flow.distinctUntilChanged حل می‌کند.

چه پارامتر beta برای XFetch انتخاب کنیم؟

beta = 1.0 — مقدار پیش‌فرض که توزیع یکنواخت محاسبات مجدد را می‌دهد. برای محافظت تهاجمی‌تر (احتمال کمتر عدم وجود)، beta را به ۱.۵-۲.۰ افزایش دهید. برای صرفه‌جویی در منابع منبع — به ۰.۵ کاهش دهید. محدوده توصیه شده: ۰.۸-۱.۲.

آیا Probabilistic Early Expiration با CDN کار می‌کند؟

بله، از طریق مکانیزم Cache-Control: stale-while-revalidate و Cache-Control: stale-if-error. CDN نسخه قدیمی را برمی‌گرداند و در پس‌زمینه کش را به‌روز می‌کند، که معادل PEE در CDN است. Cloudflare و Fastly از این دستورالعمل‌ها از سال ۲۰۲۳ پشتیبانی می‌کنند.

خلاصه

  • Cache Stampede — سیل درخواست‌ها به منبع در هنگام عدم وجود دسته‌جمعی کش، که می‌تواند باعث خرابی سیستم شود.
  • دلیل اصلی — انقضای همزمان TTL یک ورودی محبوب در میان بسیاری از مشتریان یا رشته‌ها.
  • قفل‌گذاری Mutex — اولین رشته کش را به‌روز می‌کند، بقیه منتظر می‌مانند؛ ساده اما تأخیر ایجاد می‌کند.
  • Probabilistic Early Expiration — هر مشتری به طور تصادفی کش را قبل از انقضا دوباره محاسبه می‌کند و پیک‌ها را هموار می‌کند.
  • XFetch — الگوریتم تطبیقی که احتمال محاسبه مجدد را بر اساس زمان باقی‌مانده عمر و پارامتر beta محاسبه می‌کند.
  • به‌روزرسانی پیشگیرانه — فرآیند پس‌زمینه کش را قبل از انقضا برای کلیدهای محبوب بازنویسی می‌کند و عدم وجود را از بین می‌برد.
  • بهترین محافظت برای برنامه‌های موبایل — ترکیب کش دیسکی (Room) با Stale-While-Revalidate و محافظت سرور از طریق XFetch.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید