AWS Certified CloudOps Engineer - Associate

التخزين المؤقت مع CloudFront وElastiCache

أين تضع الذاكرة المؤقتة كي ترفع حملاً حقيقياً: مفتاح التخزين في CloudFront وقواعد TTL التي تقرّر ما تحتفظ به الحافة، واستراتيجيات lazy loading وwrite-through وTTL التي تقرّر ما يحمله ElastiCache أمام قاعدة بياناتك.

متوسط 30 دقائق 5 أهداف التعلّم
  1. قرّر هل يحتاج حمل العمل ذاكرة مؤقتة عند الحافة أم أمام قاعدة البيانات أم كليهما
  2. توقّع كم يبقى الكائن في CloudFront انطلاقاً من إعدادات TTL في cache policy وترويسات Cache-Control القادمة من الأصل
  3. ارفع نسبة الإصابة في CloudFront بتقليص مفتاح التخزين، واختر بين invalidation وأسماء الملفات المُصدَّرة
  4. قارن lazy loading وwrite-through وTTL كاستراتيجيات لملء ElastiCache، وسمِّ العطل الذي تحمله كل واحدة
  5. اقرأ مقاييس ElastiCache في CloudWatch لتختار بين التوسيع الرأسي وإضافة نسخ للقراءة وإضافة shards

صفحة الكتالوج لديك تشغّل استعلاماً واحداً يستغرق 300 مللي ثانية ويعيد الصفوف المئتين نفسها لكل زائر. وعند 400 طلب في الثانية، تشغّل قاعدة البيانات ذلك الاستعلام 400 مرة في الثانية وتحرق معالجها لتنتج جواباً أنتجته قبل قليل. لا عيب في الاستعلام. المشكلة أنك تعيد حساب ثابت، والعلاج أن تحفظ الجواب في مكان أقرب من الشيء الذي حسبه.

وتعطيك AWS مكانين مختلفين تماماً لحفظه، ويسمّي Skill 2.1.2 في اختبار SOA-C03 كليهما: Amazon CloudFront عند الحافة، وAmazon ElastiCache بجوار تطبيقك. وهما ليسا بديلين عن بعضهما، واختيار الخطأ منهما يعني إضافة خدمة لا ترفع حملاً.

ذاكرتان، ومسافتان

CloudFrontElastiCache
أين تجلسمواقع الحافة في AWS، قرب الزائرداخل VPC لديك، قرب تطبيقك
ماذا تخزّناستجابات HTTP كاملة، بمفتاح تخزينما يضعه فيها كودك، بمفتاح نصّي
من يكتب فيهاCloudFront تلقائياً عند الإخفاقكود تطبيقك صراحةً
ماذا تحميالأصل والمسار الشبكي إليهقاعدة البيانات
كم يكلّف الإخفاقطلباً واحداً إلى الأصلاستعلاماً من قاعدة البيانات وكتابةً في الذاكرة
بماذا تضبطهاcache policies وقيم TTL ومفتاح التخزينالاستراتيجية وTTL ونوع العقدة وعدد shards

والسطر الذي يحسم أيّهما تختار: CloudFront ذاكرة تُعِدّها، وElastiCache ذاكرة تبرمجها. فإن كانت البايتات نفسها تخرج إلى زوّار كثيرين عبر HTTP، امتصّ CloudFront تلك الحركة بلا تغيير في الكود. وإن كان الشيء المكلف نتيجة استعلام أو كائن جلسة أو لوحة صدارة محسوبة لا يفهمها إلا تطبيقك، فلا ذاكرة عند الحافة تنفع، لأن CloudFront لا يرى داخل الاستجابة ليعرف أنها قابلة لإعادة الاستخدام.

والأنظمة الكبيرة تشغّل الاثنتين. يأخذ CloudFront الاستجابات العامة المتكرّرة، ويأخذ ElastiCache القراءات الداخلية المتكرّرة، فلا ترى قاعدة البيانات إلا ما يتغيّر فعلاً.

مفتاح التخزين في CloudFront هو ما يعرّف «الإصابة» أصلاً

يخزّن CloudFront كل كائن تحت مفتاح تخزين (cache key). وطلب الزائر إصابة فقط إن أنتج المفتاح نفسه الذي أنتجه طلب سابق وكان ذلك الكائن ما زال صالحاً في موقع الحافة. وكل ضبط للتخزين المؤقت في CloudFront يعود إلى قاعدة واحدة: قيم أقلّ في المفتاح تعني إصابات أكثر.

وتتحكّم بالمفتاح عبر cache policy مرتبطة بسلوك تخزين. تحدّد السياسة أي الترويسات وملفات تعريف الارتباط وسلاسل الاستعلام تدخل المفتاح، وتحمل معها إعدادات TTL.

والسياستان المُدارتان تستحقّان الحفظ لأنهما تعلّمان طرفَي المدى:

cache policy مُدارةMin TTLMax TTLDefault TTLمحتوى مفتاح التخزين
CachingOptimizedثانية واحدة31,536,000 ثانية (365 يوماً)86,400 ثانية (24 ساعة)لا شيء سوى ترويسة Accept-Encoding المُطبَّعة
CachingDisabled000لا شيء

فـ CachingOptimized هي الجواب الافتراضي للأصول الساكنة. وCachingDisabled هي الجواب الصحيح لأي محتوى شخصي فعلاً، وهي تعمل لأن قيم TTL الثلاث أصفار، لا لأنها لا تمرّر شيئاً.

وثلاثة أخطاء في مفتاح التخزين تكلّف نسبة إصابة حقيقية، ولكل منها علاج:

  • حالة الأحرف وترتيبها في سلاسل الاستعلام. ?parameter1=A و?parameter1=a مفتاحان مختلفان، وكذلك ?parameter1=a&parameter2=b و?parameter2=b&parameter1=a. الكائن نفسه، وحتى أربعة مدخلات في الذاكرة. وحّد حالة الأحرف والترتيب في التطبيق الذي يبني الروابط.
  • تمرير كل ملفات تعريف الارتباط. مع كل ملف تمرّره يخزّن CloudFront نسخة منفصلة لكل تركيبة اسم وقيمة. فملفان بثلاث قيم محتملة لكلٍّ منهما يعنيان حتى 9 نسخ من ملف .css واحد. افصل المحتوى الساكن عن الديناميكي في سلوكَي تخزين مختلفين، ومرّر ملفات تعريف الارتباط على الديناميكي وحده.
  • التخزين حسب User-Agent. له عدد هائل من القيم المتمايزة، فهو يعطّل التخزين عملياً بينما يبدو إعداداً سليماً. وإن احتجت استجابات واعية بالجهاز، فخزّن حسب ترويسات الأجهزة في CloudFront: CloudFront-Is-Desktop-Viewer وCloudFront-Is-Mobile-Viewer وCloudFront-Is-SmartTV-Viewer وCloudFront-Is-Tablet-Viewer. أربع قيم لا آلاف، وتستطيع تمرير ما يغيّر الاستجابة منها فقط.

ورافعة أخيرة تجلس خارج المفتاح. إن لم يكن الضغط مستخدَماً، اربط ترويسة أصل مخصّصة اسمها Accept-Encoding بقيمة فارغة. عندها يسقط CloudFront تلك الترويسة من المفتاح كلياً بدل شطر كل كائن إلى نسخ Gzip وBrotli وغير مضغوطة.

كم يبقى الكائن: الأصل يقترح والسياسة تقرّر

هذا هو الجزء الذي يخطئ فيه المشغّلون، ويستحقّ المشي عليه ببطء.

ثلاثة إعدادات تسكن في cache policy: Minimum TTL وMaximum TTL وDefault TTL. ويستطيع الأصل أيضاً إرسال Cache-Control: max-age أو Cache-Control: s-maxage أو ترويسة Expires. ولا ينتصر أحد الطرفين مطلقاً. الأصل يقترح مدة والسياسة تقيّدها داخل المدى.

امشِ عليها بسياسة فيها Minimum TTL يساوي 60 وMaximum TTL يساوي 86400 وDefault TTL يساوي 3600:

استجابة الأصليخزّن CloudFront لمدةلماذا
Cache-Control: max-age=1060 ثانيةتحت الحدّ الأدنى فتُرفَع إلى Minimum TTL
Cache-Control: max-age=72007200 ثانيةداخل المدى فتُحترَم كما هي
Cache-Control: max-age=3153600086400 ثانيةفوق الحدّ الأقصى فتُخفَض إلى Maximum TTL
لا ترويسة Cache-Control3600 ثانيةلم يُقترَح شيء فتنطبق Default TTL

وثلاث تفاصيل على أطراف هذا الجدول:

  • s-maxage تتقدّم على max-age عند الحافة. فحين يرسل الأصل الاثنتين، يقيّد CloudFront قيمة s-maxage ويستخدم المتصفّح max-age. وهكذا تخزّن كائناً ساعةً عند الحافة ودقيقةً في المتصفّح.
  • Expires هي الخيار الأضعف. فإن أرسل الأصل Cache-Control: max-age وExpires معاً، استخدم CloudFront قيمة max-age وحدها. وAWS توصي بـ max-age.
  • الزائر لا يستطيع فرض تحديث. يتجاهل CloudFront ترويستَي Cache-Control وPragma في طلبات الزوّار، فإعادة التحميل القسرية في المتصفّح لا تنظّف الحافة.

والآن الوهم الذي يشحن أعطالاً. من المغري افتراض أن Cache-Control: no-store من الأصل يمنع CloudFront من التخزين دائماً. وهو لا يمنعه حين تكون Minimum TTL في cache policy أكبر من صفر. في تلك الحالة يخزّن CloudFront الكائن طوال المدة الدنيا رغم أن الأصل قال لا تخزّن، وهكذا تصل صفحة حساب شخصية إلى الزائر التالي الذي يضرب موقع الحافة نفسه. وتحمل سياستا CachingOptimized (بحدّ أدنى ثانية واحدة) وAmplify (بحدّ أدنى ثانيتين) هذا التحذير في وثائق AWS. وإن كان المحتوى ممنوعاً من التخزين، فالجواب سياسة حدّها الأدنى صفر.

تقديم محتوى قديم عن قصد

توجيهان يتيحان لك مقايضة قليل من الطزاجة بزمن استجابة أفضل وبالنجاة من تعطّل الأصل:

Cache-Control: max-age=3600, stale-while-revalidate=600, stale-if-error=86400
  • في الساعة الأولى يقدّم CloudFront من الذاكرة كالمعتاد.
  • بعدها يتيح stale-while-revalidate=600 لـ CloudFront أن يسلّم الزائر النسخة القديمة فوراً بينما يجلب نسخة طازجة في الخلفية، لمدة تصل إلى 10 دقائق.
  • ويتيح stale-if-error=86400 له مواصلة تقديم النسخة القديمة حتى 24 ساعة إن كان الأصل غير قابل للوصول أو يعيد خطأ 5xx.

والاثنان مقيّدان بـ Maximum TTL في السياسة، أيّهما أقلّ. وبعد الحدّ الأقصى يختفي الكائن من الحافة مهما طلبت التوجيهات.

invalidation مقابل أسماء الملفات المُصدَّرة

حين تحتاج إخراج محتوى من الذاكرة قبل انتهاء صلاحيته، أمامك خياران، وتوصي AWS بالذي يقصده الناس ثانياً.

invalidation تزيل الملفات من ذواكر الحافة الآن. وأول 1,000 مسار شهرياً مجانية لكل حساب AWS عبر كل التوزيعات، وتدفع لكل مسار بعدها. والمسار الذي يحمل الرمز * يُحتسَب مساراً واحداً مهما بلغ عدد الملفات التي يزيلها، فـ /* وحدة محاسبة واحدة. ويجب أن يكون الرمز البديل آخر محرف؛ فالنجمة في أي موضع آخر تُطابَق حرفياً.

وفخّان في invalidation يهدران عملية نشر:

  • سلاسل الاستعلام جزء من المسار. فإن كان مفتاح التخزين يشمل سلاسل الاستعلام، لم يُبطِل /images/logo.jpg المسارَ /images/logo.jpg?v=2. استخدم /images/logo.jpg*.
  • مسارات المجلدات تحتاج الصيغتين. فإن لم تكن روابطك متّسقة في الشرطة المائلة الأخيرة، أبطِل /images و/images/ معاً.

وأسماء الملفات المُصدَّرة تعني شحن app.a3f91c.js بدل الكتابة فوق app.js. وتوصي AWS بهذا نهجاً أساسياً للمحتوى كثير التغيّر، والسبب ليس الكلفة وحدها. فـ invalidation تنظّف CloudFront ولا تفعل شيئاً للنسخة الجالسة في متصفّح الزائر أو في وكيل الشركة؛ أما الاسم الجديد فيغيّر الرابط، فتُخفِق كل ذاكرة في السلسلة. وهو يجعل التراجع تافهاً ويجعل سجلّات الوصول مقروءة، لأن سطر السجلّ يسمّي الإصدار الذي قُدِّم.

وOrigin Shield هي الرافعة البنيوية الأخرى. تضع طبقة تخزين إضافية أمام الأصل بحيث تصبّ كل طبقات CloudFront، مواقع الحافة والذواكر الإقليمية، في موقع واحد. عندها يخدم الأصل طلباً واحداً لكل كائن بدل طلب لكل ذاكرة إقليمية.

ElastiCache: الذاكرة التي عليك برمجتها

يملأ CloudFront نفسه بنفسه. وElastiCache لا يفعل: كودك يقرّر ما يدخل ومتى وكم يبقى. وتوثّق AWS ثلاث استراتيجيات، ويختبر الاختبار عطل كل واحدة لا تعريفها.

lazy loading لا تكتب في الذاكرة إلا بعد إخفاق.

get_customer(customer_id)
    customer_record = cache.get(customer_id)
    if (customer_record == null)
        customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id)
        cache.set(customer_id, customer_record)
    return customer_record

فلا يدخل الذاكرة إلا ما طُلب فعلاً، وعطل العقدة قابل للنجاة منه: العقدة الفارغة الجديدة ما زالت تعيد أجوبة صحيحة، أبطأ فحسب، بينما تعيد الإخفاقات ملأها. والثمن عقوبة إخفاق من ثلاث رحلات (قراءة من الذاكرة، واستعلام من قاعدة البيانات، وكتابة في الذاكرة) وبيانات قديمة، لأن لا شيء يحدّث الذاكرة حين تتغيّر قاعدة البيانات تحتها.

وwrite-through تكتب في الذاكرة مع كل كتابة في قاعدة البيانات.

save_customer(customer_id, values)
    customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values)
    cache.set(customer_id, customer_record)
    return success

فالبيانات المخزّنة لا تتقادم أبداً، ويهبط زمن الاستجابة الإضافي على الكتابات حيث يحتمله المستخدمون أكثر. والثمن صورة معكوسة لثمن lazy loading: بيانات ناقصة على أي عقدة جديدة، لأن العقدة الطازجة لا تحمل شيئاً حتى تُكتَب الصفوف المقابلة مرة أخرى، وامتلاء الذاكرة بقيم كُتبت وقد لا تُقرأ أبداً.

وإضافة TTL إلى كل كتابة هي ما يجعل الاستراتيجيتين تعملان معاً. فـ TTL تسقّف كم يستطيع مدخل lazy loading أن يتقادم، وتخلي مدخلات write-through التي لا يقرأها أحد. والنمط الإنتاجي هو الثلاثة معاً: write-through للصحّة، وlazy loading للمرونة، وTTL لتسقيف الاثنتين.

cache.set(customer_id, customer_record, 300)   # تنتهي صلاحيته بعد 5 دقائق

وTTL لا تضمن الطزاجة أبداً. هي تضمن سقفاً للتقادم، وهذا وعد مختلف وأسهل تحقيقاً بكثير. قل ذلك في مراجعة التصميم بدل ترك جملة «نخزّن لخمس دقائق» تُفهَم على أنها «الذاكرة صحيحة».

اختيار المحرّك، ولماذا يغيّر خيارات التوسيع لديك

يدعم ElastiCache محرّكات Memcached وValkey وRedis OSS. والمحرّك الذي تختاره يحدّد أي إجراءات التوسيع موجودة أصلاً، فهذه ليست مسألة تفضيل.

MemcachedValkey / Redis OSS (وضع cluster معطّل)Valkey / Redis OSS (وضع cluster مفعّل)
أنواع البياناتنصوص وكائنات بسيطةمركّبة (قوائم، جداول تجزئة، مجموعات، مجموعات مرتّبة)مركّبة
متعدّد الخيوطنعملالا
النسخ المتماثلة للقراءةلانعمنعم
التبديل التلقائي عند الفشللااختياريإلزامي
تقسيم البيانات على shardsنعم، من جانب العميللانعم
إعادة التوزيع دون توقّفلالانعم
النسخ الاحتياطي والاستعادةالعناقيد المبنيّة على عقد: لانعمنعم

اختر Memcached حين تريد أبسط نموذج ممكن وعقداً كبيرة متعدّدة النوى وتخزيناً بسيطاً للكائنات. واختر Valkey أو Redis OSS حين تحتاج نسخاً متماثلة أو تبديلاً عند الفشل أو استمرارية أو مجموعات مرتّبة أو pub/sub. والنتيجة العملية: عنقود Memcached لا يُصلَح بإضافة نسخ للقراءة لأنه لا يملكها. ورافعتاه الوحيدتان نوع عقدة أكبر أو عقد أكثر.

قراءة المقاييس، ثم اختيار إجراء التوسيع

أسئلة توسيع ElastiCache تشخيصية عادةً. المقياس يقول لك أي مورد شحيح، والمورد مع المحرّك يقولان لك الإجراء.

المقياسماذا يعنيإلى أين يشير
CPUUtilizationنسبة المعالج على مستوى المضيفسقف حمل العمل على العقد الصغيرة
EngineCPUUtilizationاستهلاك نواة المحرّك المفردةالإشارة الحقيقية على العقد ذات 4 معالجات افتراضية فأكثر
Evictionsمفاتيح أُزيلت لإفساح مكانالذاكرة لا تكفي مجموعة العمل
SwapUsage وFreeableMemoryضغط الذاكرةFreeableMemory تحت 100 ميغابايت، أو SwapUsage فوق FreeableMemory، تعني عقدة في ورطة
ReplicationLagكم تتخلّف النسخة عن الأساسيةقراءات النسخة تعيد بيانات قديمة
TrafficManagementActiveالقيمة 1 تعني أن ElastiCache يخنق الأوامر الواردةالعقدة أصغر مما يحتاجه الحمل

وعتبة المعالج تُوقِع الناس، فامشِ عليها. يشغّل Valkey وRedis OSS المحرّك على خيط واحد، فتستطيع العقدة أن تكون مشبعة تماماً بينما يقرأ معالج المضيف أقلّ من 100 بالمئة بكثير. وتعطيك AWS الحسبة: اضبط العتبة عند 90 مقسومة على عدد النوى. وعلى عقدة بنواتين تصير 45 بالمئة. فالعنقود الجالس عند 46 بالمئة مع زمن استجابة يرتفع ليس صحيحاً وخاملاً، بل مثبّتاً عند سقفه. وعلى أنواع العقد ذات 4 معالجات افتراضية فأكثر، استخدم EngineCPUUtilization فتختفي القسمة. أما Memcached فمتعدّد الخيوط، وعتبته نحو 90 بالمئة فعلاً.

وحين تعرف المورد، يتبع الإجراء المحرّكَ وحملَ العمل:

  • قراءة كثيفة وفوق العتبة، على Valkey أو Redis OSS: أضف نسخاً للقراءة.
  • كتابة كثيفة، ووضع cluster معطّل: وسّع رأسياً إلى نوع عقدة أكبر. فالأساسية واحدة، والكتابات الأكثر تحتاج أساسية أكبر.
  • كتابة كثيفة، ووضع cluster مفعّل: أضف shards، فتتوزّع الكتابات على عقد أساسية أكثر.
  • أي ضغط على Memcached: نوع عقدة أكبر، أو عقد أكثر.

كيف يحدث التوسيع فعلاً

في وضع cluster المفعّل، تغيّر إعادة التوزيع دون توقّف عددَ shards بينما يواصل العنقود خدمة الطلبات. فإضافة shards ترفع سعة القراءة والكتابة، وإزالتها تخفض الكلفة، وإعادة الموازنة تسوّي مساحة المفاتيح على shards القائمة. وثلاثة قيود تستحقّ المعرفة: الـ shards الجديدة تأخذ عدد العقد نفسه الذي في أصغر shard قائم، ولا تستطيع ضبط مساحات مفاتيح لكل shard دون توقّف (وهذا يتطلّب مسار النسخ الاحتياطي والاستعادة)، والمفاتيح التي تحمل عناصر أكبر من 256 ميغابايت بعد التسلسل لا تُهاجَر، فقد تبقى shards غير متوازنة. وقبل إزالة shards، يتحقّق ElastiCache من أن ما تبقّى يستوعب البيانات ويلغي العملية بدل فقدان مفاتيح.

والتوسيع الرأسي بتغيير نوع العقدة عملية دون توقّف أيضاً على Valkey وRedis OSS. أما إعادة التوزيع مع توقّف، أي مسار النسخ الاحتياطي والاستعادة، فهي التي تُظلِم، ولا تقبل ذلك التوقّف إلا حين تحتاج ما تتيحه وحدها: تغيير نوع العقدة وإصدار المحرّك وعدد النسخ لكل shard ومساحات المفاتيح في حركة واحدة.

وElastiCache Serverless يزيل قرار shards من يدك. يتتبّع المعالج والذاكرة والشبكة باستمرار ويضيف shards من تلقائه، وتراقب أنت مقياسين بدل قائمة عقد: BytesUsedForCache للتخزين وElastiCacheProcessingUnits (ECPUs) للحوسبة. وتستطيع تسقيف الاثنين لتقييد الكلفة، لكن افهم ما يفعله السقف عند الحدّ: بلوغ الحدّ الأقصى للتخزين يجعل ElastiCache يخلي المفاتيح ذات TTL بترتيب LRU ثم يعيد أخطاء نفاد ذاكرة، وبلوغ الحدّ الأقصى للـ ECPUs يجعله يخنق الطلبات. وتوصي AWS بتنبيه CloudWatch عند 75 بالمئة من أي حدّ أقصى تضبطه، كي تعرف قبل مستخدميك.

نصائح للاختبار

  • «الاستجابة نفسها لزوّار كثيرين عبر HTTP» هي CloudFront. و«نتيجة استعلام مكلفة يعيد التطبيق استخدامها» هي ElastiCache. والنصّ الذي يصف قاعدة بيانات تحت ضغط قراءة لا يسأل عن الحافة.
  • إن أُعطيت أرقام TTL فطبّق التقييد: قيمة max-age تحت Minimum TTL تُرفَع إلى الحدّ الأدنى، وفوق Maximum TTL تُخفَض إلى الحدّ الأقصى، وDefault TTL لا تنطبق إلا حين لا يرسل الأصل max-age. والأجوبة الخاطئة تلتقط آخر رقم ذُكر.
  • «الأصل يرسل no-store ومع ذلك يُقدَّم محتوى قديم» يشير إلى cache policy حدّها الأدنى أكبر من صفر، لا إلى خلل في CloudFront.
  • 1,000 مسار invalidation مجاني شهرياً لكل حساب، والمسار بالرمز البديل يُحتسَب واحداً. وإن شدّد النصّ على تحديثات متكرّرة، فالجواب المقصود عادةً أسماء الملفات المُصدَّرة لا مزيد من invalidation.
  • أي خيار يرفع نسبة الإصابة بإضافة ترويسات أو ملفات تعريف ارتباط أو سلاسل استعلام إلى مفتاح التخزين خيار خاطئ. الإصابات تأتي من مفتاح أصغر.
  • lazy loading تسمح ببيانات قديمة وتنجو من العقد الفارغة. وwrite-through طازجة دائماً وتتعثّر عند العقد الفارغة. وTTL هي ما يجعل تشغيل الاثنتين ممكناً. طابق العَرَض بالاستراتيجية لا التعريف.
  • Valkey وRedis OSS أحاديّا الخيط: عتبة تنبيه المعالج هي 90 مقسومة على عدد النوى، وEngineCPUUtilization هي الإشارة الأنظف على العقد الأكبر.
  • Memcached بلا نسخ متماثلة وبلا تبديل عند الفشل وبلا وضع cluster. وأي جواب يعرض هذه على عنقود Memcached خاطئ على حدّ المحرّك وحده.
  • القراءة الكثيفة تعني نسخاً للقراءة، والكتابة الكثيفة مع وضع cluster معطّل تعني عقدة أكبر، والكتابة الكثيفة مع وضع cluster مفعّل تعني shards أكثر.

القاعدة التي تخرج بها من هذا الدرس: سمِّ الشيء المتكرّر قبل أن تسمّي الخدمة. فاستجابات HTTP المتكرّرة مكانها الحافة، ونتائج الاستعلامات المتكرّرة مكانها الذاكرة بجوار التطبيق، وما يتغيّر فعلاً مع كل طلب لا مكان له إلا قاعدة البيانات. وهذه الفئة الأخيرة هي ما لا ينقذه التخزين المؤقت، ومنها يبدأ الدرس التالي، على توسيع قاعدة البيانات العلائقية نفسها.