منع Cache Stampede في أنظمة إدارة المحتوى: Locks وStale-While-Revalidate وJitter
تظهر مشكلة cache stampede عندما ينتهي مفتاح cache شديد الشعبية فتصل طلبات كثيرة في اللحظة نفسها، وتقرر جميعها إعادة بناء القيمة من قاعدة البيانات أو API. في CMS كبير قد يحول انتهاء مفتاح واحد إلى موجة استعلامات تكفي لإسقاط قاعدة البيانات.
1. كيف يحدث الـ Stampede؟
لنفترض أن الصفحة الرئيسية مخزنة لمدة 60 ثانية ويصل إليها 1000 طلب في الثانية. عند الثانية 61 تصبح القيمة MISS. إذا لم توجد حماية، قد يبدأ عدد كبير من الطلبات في تنفيذ نفس الاستعلامات المكلفة بالتوازي.
2. استخدم Lock حول عملية إعادة البناء
الفكرة أن يسمح النظام لعملية واحدة فقط ببناء القيمة الجديدة، بينما تنتظر العمليات الأخرى أو تستخدم نسخة سابقة:
if cache_miss:
acquire_lock(key)
recheck_cache()
if still_missing:
value = rebuild()
cache_put(value)
release_lock()
إعادة فحص cache بعد الحصول على القفل مهمة؛ فقد تكون عملية أخرى قد بنت القيمة أثناء الانتظار.
3. استخدم stale-while-revalidate
بدل إجبار المستخدم على انتظار إعادة البناء، اسمح بإرجاع نسخة قديمة لفترة قصيرة أثناء تحديثها. RFC 5861 يعرّف هذا السلوك على مستوى HTTP، وLaravel يوفر نمطًا مشابهًا عبر Cache::flexible.
Cache::flexible('homepage', [60, 300], fn () => buildHomepage());
خلال أول 60 ثانية تكون القيمة fresh. بعدها يمكن إرجاع stale لفترة محدودة بينما تُجدَّد.
4. أضف Jitter إلى TTL
إذا أنشأت آلاف المفاتيح بنفس TTL، ستنتهي في وقت متقارب. أضف هامشًا عشوائيًا:
ttl = base_ttl + random(0, 30)
هذا يقلل احتمالية موجة انتهاء متزامنة.
5. لا تجعل Lock أطول من اللازم
القفل الطويل قد يتحول إلى نقطة اختناق. اختر مدة تغطي أسوأ زمن منطقي لإعادة البناء، وتعامل مع حالات crash بحيث ينتهي القفل تلقائيًا.
6. ضع خطة عند فشل المصدر
إذا فشل الاستعلام أو API أثناء التجديد، من الأفضل أحيانًا الاستمرار في تقديم نسخة stale بدل إرجاع خطأ كامل، خصوصًا للمحتوى العام غير الحساس. لكن يجب تحديد حد زمني واضح حتى لا تبقى نسخة قديمة بلا نهاية.
7. اختبر تحت الضغط
اختبار وظيفي عادي لن يكشف المشكلة. نفّذ load test يتضمن:
- تسخين cache.
- إجبار المفتاح على الانتهاء.
- إرسال عدد كبير من الطلبات المتزامنة.
- قياس عدد مرات تنفيذ rebuild الحقيقي.
- مراقبة قاعدة البيانات وRedis وزمن الاستجابة.
النتيجة الجيدة ليست مجرد عدم وجود أخطاء؛ المطلوب أن تكون عملية rebuild الواحدة أو العدد المحدود منها هي التي نفذت فعلًا.
8. راقب مؤشرات الخطر
- ارتفاع مفاجئ في DB queries عند حدود TTL.
- قفزات دورية في latency.
- زيادة locks المحتجزة أو timeouts.
- ارتفاع MISS rate في نفس اللحظة لعدد كبير من المفاتيح.
- ارتفاع CPU في التطبيق بعد purge شامل.
مراجع
HTTP Cache وCDN في أنظمة إدارة المحتوى: Cache-Control وETag وVary عمليًا
Cache Invalidation في أنظمة إدارة المحتوى: كيف تمنع ظهور محتوى قديم بعد النشر؟
Redis كطبقة Cache للـ CMS: تصميم المفاتيح وTTL وسياسات Eviction
منع Cache Stampede في أنظمة إدارة المحتوى: Locks وStale-While-Revalidate وJitter