الإجابة المختصرة
في Cache-Aside يقرأ التطبيق من الكاش أولاً ويملأه عند الفشل، وفي Write-Through يكتب في الكاش وقاعدة البيانات معاً، وفي Write-Back يكتب في الكاش ويؤجل كتابة قاعدة البيانات للدفعة. وتدافع الكاش يحدث عندما تنتهي صلاحية مفتاح شائع فتهجم آلاف الطلبات المتزامنة على قاعدة البيانات في نفس اللحظة.
الشرح
استراتيجية Cache-Aside هي الأكثر شيوعاً ومرونة. Write-Through تضمن أن الكاش محدث دائماً لكنها تزيد زمن الكتابة. Write-Back سريعة جداً في الكتابة لكنها مهددة بفقدان البيانات إذا تعطل الكاش قبل تفريغها في الذاكرة الدائمة. لمنع Cache Stampede (تدافع الكاش / Thundering Herd): (1) استخدام أقفال المزامنة الموزعة (Mutex) بحيث يقوم طلب واحد فقط بإعادة الاستعلام من قاعدة البيانات وتحديث الكاش بينما ينتظر البقية. (2) إضافة تباين عشوائي لوقت انتهاء الصلاحية (Jitter / Randomized TTL). (3) إعادة الحساب المبكر في الخلفية (Probabilistic Early Expiration).
مثال بالكود
async function getWithStampedeProtection(key) {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const lockKey = `lock:${key}`;
const token = crypto.randomUUID();
if (await redis.set(lockKey, token, "EX", 5, "NX")) { // only one caller rebuilds the entry
try {
const fresh = await fetchFromDB();
const ttl = 3600 + Math.floor(Math.random() * 300); // jitter: keys don't all expire together
await redis.set(key, JSON.stringify(fresh), "EX", ttl);
return fresh;
} finally {
// release only OUR lock (compare-and-delete), never one acquired by someone else after ours expired
await redis.eval(
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end",
1, lockKey, token,
);
}
}
await new Promise((resolve) => setTimeout(resolve, 50)); // everyone else waits briefly…
return getWithStampedeProtection(key); // …and then reads the refreshed cache
}أخطاء شائعة
- تعيين نفس وقت انتهاء الصلاحية (TTL) الدقيق لجميع المفتايح الحيوية في نفس الثانية، مما يسبب سقوطها جميعاً في وقت واحد وانهيار قاعدة البيانات.