أين تخزّن مؤقتاً: الطبقات المختلفة
أين تخزّن مؤقتاً: الطبقات المختلفة
التخزين المؤقت ليس مجرد زرّ واحد تضغطه — بل هو مجموعة من القرارات المتراكمة. يمكن تخزين البيانات نفسها مؤقتاً في أربعة أماكن مختلفة خلال رحلتها من قاعدة بيانات في فيرجينيا إلى متصفح في طوكيو. لكلّ طبقة تكلفتها وحجم تأثيرها ومجموعة مقايضاتها الخاصة. معرفة أين تُخزّن مؤقتاً لا تقلّ أهمية عن معرفة ماذا تُخزّن.
الطبقة الأولى — ذاكرة التخزين المؤقت على جهاز العميل
أسرع ذاكرة تخزين مؤقت هي تلك التي لا تغادر جهاز المستخدم أصلاً. تخزّن المتصفحات استجابات HTTP في ذاكرة تخزين محلية (قرص أو RAM) تتحكم بها بالكامل ترويسات الاستجابة التي يُرسلها خادمك:
Cache-Control: max-age=86400— يجوز للمتصفح تقديم هذه الاستجابة لمدة تصل إلى 24 ساعة دون الذهاب إلى الشبكة إطلاقاً.ETag/Last-Modified— إعادة التحقق الشرطية: يسأل المتصفح "هل تغيّر هذا المحتوى؟" ولا يُنزّل الجسم إلا إذا ردّ الخادم بالإيجاب (الاستجابة304 Not Modifiedتوفّر عرض النطاق حتى على المورد القديم).
التخزين المؤقت على جهاز العميل خاص بكل مستخدم. ملف CSS مخزّن في متصفح مستخدم واحد لا يفيد زائراً آخر في زيارته الأولى. لكن للزوار العائدين يلغي رحلات الشبكة بالكامل — زمن الاستجابة يصبح صفراً عند الإصابة فعلياً. تُشير Google إلى أن نحو 85 % من موارد الويب قابلة للتخزين المؤقت، ومع ذلك كثير من المواقع ترسل Cache-Control: no-store على كل شيء بشكل افتراضي، مما يُهدر أداءً هائلاً.
app.a3f91c.js (هاش المحتوى في اسم الملف). ثم قدّمها بـ Cache-Control: max-age=31536000, immutable. حين يتغير الملف تتغير رابطه — فيجلب المتصفح الإصدار الجديد فوراً، بينما يظل الإصدار القديم مخزّناً إلى أجل غير مسمى لمن يملكه بالفعل.
الطبقة الثانية — CDN / ذاكرة التخزين على الحافة
تدير شبكة توصيل المحتوى (CDN) مئات نقاط الحضور (PoPs) حول العالم. حين يطلب مستخدم في سنغافورة موقعك المستضاف في فرانكفورت، يُقدّم CDN الاستجابة المخزّنة مؤقتاً من نقطة حضور في سنغافورة — مما يُخفّض زمن الاستجابة من ~200 مللي ثانية إلى ~5 مللي ثانية.
ذاكرات CDN مشتركة: كل مستخدم يصل إلى نقطة الحضور نفسها يستفيد من ذاكرة دافئة أعدّها زائر سابق. وهي مثالية للأصول والصفحات المتطابقة لجميع المستخدمين: الصور، الخطوط، حزم JavaScript، وصفحات HTML غير الشخصية، واستجابات API القابلة للمشاركة.
تشغّل CDNs الكبرى — Cloudflare وAWS CloudFront وFastly — منطقاً على الحافة (edge functions, edge workers) يتيح لك التخصيص الخفيف أو اختبار A/B دون رحلة ذهاب-إياب إلى خادم الأصل.
Cache-Control ذاتها، لكن CDNs تحترم أيضاً s-maxage (TTL للذاكرة المشتركة) الذي يتيح تعيين TTL أقصر للمتصفحات مع الاحتفاظ بـ TTL أطول عند الـ CDN.
الطبقة الثالثة — ذاكرة التخزين على مستوى التطبيق
حين يصل الطلب إلى خوادمك تملك خيارين فرعيين:
- ذاكرة التخزين داخل العملية (In-Process) — خريطة هاش داخل العملية الجارية. مكتبات مثل Caffeine وGuava Cache (Java) أو
functools.lru_cache(Python) تخزّن أزواج مفتاح-قيمة في كومة التطبيق نفسه. الإصابة تكلف بضع مئات من النانو ثانية — تكاد تكون مجانية. المقايضة: كل خادم تطبيق يمتلك ذاكرته المستقلة. على 10 خوادم قد تُخزَّن البيانات ذاتها 10 مرات، والإلغاء صعب عبر الخوادم. - ذاكرة التخزين عبر بروكسي عكسي — أدوات كـ Varnish أو Nginx يمكنها الجلوس أمام تطبيقك وتخزين استجابات HTTP كاملة. التطبيق نفسه لا يعمل أصلاً عند الإصابة.
ذاكرات In-Process الأنسب لـ بيانات المرجع الثابتة أو نادرة التغيير — رموز البلدان، الأعلام، كائنات الإعداد — حيث تكلفة البيانات القديمة منخفضة وتكرار القراءة عالٍ جداً.
الطبقة الرابعة — ذاكرة التخزين الموزعة (Redis / Memcached)
طبقة تخزين مؤقت مخصصة — عادةً Redis أو Memcached — تجلس بين تطبيقك وقاعدة البيانات. بخلاف ذاكرات In-Process، تشترك جميع خوادم التطبيق في نفس كلستر التخزين المؤقت. مفتاح يكتبه أي خادم يصبح قابلاً للقراءة فوراً من أي خادم آخر.
هذه هي ذاكرة التخزين الأكثر استخداماً في الإنتاج:
- الإصابة تكلف ~0.5–2 مللي ثانية (استدعاء شبكي) بدلاً من 5–50 مللي ثانية لاستعلام قاعدة بيانات.
- على مقياس تويتر (بيانات 2013)، بيانات تغريدة رائجة تُجلب عشرات الآلاف من المرات في الثانية — لا يمكن لقاعدة البيانات تلبية ذلك؛ Memcached يستطيع.
- Redis يضيف هياكل بيانات غنية (مجموعات مرتبة، تدفقات، pub/sub) تجعله يتجاوز مجرد التخزين المؤقت — تحديد المعدل، تخزين الجلسات، لوحات المتصدرين.
الطبقة الخامسة — ذاكرة التخزين على مستوى قاعدة البيانات
محرك قاعدة البيانات نفسه يمتلك ذاكرات تخزين مؤقت يمكنك ضبطها دون تغيير أي كود في التطبيق. InnoDB Buffer Pool في MySQL هو منطقة RAM تحتفظ بالصفحات التي جرى الوصول إليها مؤخراً (صفوف + صفحات فهارس). حين تكون صفحة استعلام موجودة بالفعل في مجمع المخزن المؤقت، لا تتم أي عمليات I/O للقرص — يُعيد المحرك النتائج من الذاكرة. ضبط هذا الحجم بصواب (عادةً 70–80 % من RAM المتاح على خادم DB مخصص) هو أحد أعلى تحسينات أداء قاعدة البيانات مردوداً.
تقدّم بعض قواعد البيانات أيضاً ذاكرة نتائج الاستعلام (مثل Query Cache الذي أُزيل من MySQL، وplan cache في PostgreSQL). هذه إلى حدٍّ كبير متروكة في المحركات الحديثة لأنها تسببت في مشاكل تعارض الأقفال — طبقات التخزين الصريحة أعلاه تمنحك تحكماً أكبر.
اختيار الطبقة (أو الطبقات) المناسبة
في الواقع العملي تستخدم طبقات متعددة في آنٍ واحد. طلب صورة منتج قد يسير هكذا: ذاكرة المتصفح (0 مللي ثانية) ← إخفاق ← ذاكرة CDN (4 مللي ثانية) ← إخفاق ← Redis (1 مللي ثانية) ← إخفاق ← مجمع مخزن DB (2 مللي ثانية) ← إخفاق ← قراءة من القرص (10 مللي ثانية). الهدف أن تُوقَف الغالبية العظمى من الطلبات عند إحدى الطبقات المبكرة والأرخص.
في الدرس التالي سنتعمق في كيفية دخول البيانات وخروجها من كل ذاكرة — استراتيجيات Read-Through وWrite-Through وWrite-Behind.