مستوى المقال: متوسط — يفترض إنك تعرف HTTP caching الأساسي، Cache-Control header، وفكرة الـ CDN edge.
لو endpoint بيرجع نفس البيانات لكل user وبياخد 380ms كل دقيقة لمّا الـ cache TTL يخلص، المشكلة مش في الـ origin — هي إنك بتجبر كل زائر يستنى الـ refresh. سطرين في Cache-Control بيخلّوا 99% من الطلبات ترجع في 4ms، والـ refresh يحصل في الخلفية بدون ما يعرف عنه حد.
stale-while-revalidate: السطرين اللي بيوفّروا 99% من زمن استجابة الـ API
المشكلة باختصار
الـ cache التقليدي بـ Cache-Control: max-age=60 بيشتغل كده: لمدة 60 ثانية كل response يرجع من الـ edge في 4ms. لكن لمّا الستين ثانية يخلصوا، أول طلب جاي بيقعد يستنى الـ origin يرد ويملا الـ cache من جديد. المستخدم اللي بيدفع التذكرة دي هو اللي بيلاقي 380ms latency من غير ذنب.
الموقف بيتكرر كل دقيقة، على كل cache key، وعلى كل CDN node. لو عندك 200 cache key نشط على 18 CDN PoP، كل دقيقة فيه حوالي 3,600 user شايفين بطء غير ضروري.
المفهوم — مثال المخبز قبل التعريف العلمي
تخيّل إنك بتروح مخبز كل صباح. عند الخبّاز رف فيه عيش ساخن متجدّد كل ساعة (هذا هو الـ cache). لمّا الساعة تخلص، خياران:
- الطريقة التقليدية (max-age العادي): الخبّاز يقولك "استنى 10 دقايق لحد ما أعمل عيش جديد." انت بتقف في طابور وانت معاك ميتنج بعد ربع ساعة.
- stale-while-revalidate: الخبّاز يديك العيش اللي عمره ساعة وربع (لسه طازة فعليًا، مجرد إنه تعدّى الـ TTL بدقيقة)، وفي نفس اللحظة بيبدأ يخبز batch جديد للزبون اللي بعدك. انت اتنقلت في 4 ثواني، والـ batch الجديد جاهز للي بعدك.
اللي حصل: بدّلنا "زبون بيستنى" بـ "زبون بياخد نسخة قديمة بدقيقة ويمشي." لو فرق دقيقة في عمر البيانات مش مشكلة عندك — وغالبًا مش مشكلة في 80% من الـ API endpoints — انت كسبت 99% من الزمن بدون ما تخسر حاجة.
التعريف العلمي من RFC 5861
الـ directive stale-while-revalidate=N اتعرّفت في RFC 5861 (IETF, أبريل 2010) ودلوقتي بقت standard في كل CDN كبير (Cloudflare, Fastly, Akamai, AWS CloudFront, Vercel Edge) ومدعومة في كل المتصفحات الحديثة من Chrome 75 و Firefox 68 وفوق.
الإعداد Cache-Control: max-age=60, stale-while-revalidate=600 معناه بالظبط:
- أول 60 ثانية: الـ response fresh، يرجع من cache مباشرة.
- من الثانية 61 لحد الثانية 660: الـ response stale لكن لسه valid. الـ cache بيرجع النسخة القديمة فورًا للـ user، وفي نفس اللحظة بيعمل request في الخلفية للـ origin علشان يجدّد الـ cache. الـ user مش بيستنى الـ refresh.
- بعد 660 ثانية: الـ entry expired نهائيًا. الطلب الجاي هيستنى origin response (نفس سلوك max-age العادي).
الفكرة الجوهرية اللي لازم تركّز فيها: فصل "متى أرجّع response للـ user" عن "متى أحدّث الـ cache". دي اللي بتشيل الـ latency من الـ critical path.