AWS Certified CloudOps Engineer - Associate
التخزين المؤقت مع CloudFront وElastiCache
أين تضع الذاكرة المؤقتة كي ترفع حملاً حقيقياً: مفتاح التخزين في CloudFront وقواعد TTL التي تقرّر ما تحتفظ به الحافة، واستراتيجيات lazy loading وwrite-through وTTL التي تقرّر ما يحمله ElastiCache أمام قاعدة بياناتك.
- قرّر هل يحتاج حمل العمل ذاكرة مؤقتة عند الحافة أم أمام قاعدة البيانات أم كليهما
- توقّع كم يبقى الكائن في CloudFront انطلاقاً من إعدادات TTL في cache policy وترويسات Cache-Control القادمة من الأصل
- ارفع نسبة الإصابة في CloudFront بتقليص مفتاح التخزين، واختر بين invalidation وأسماء الملفات المُصدَّرة
- قارن lazy loading وwrite-through وTTL كاستراتيجيات لملء ElastiCache، وسمِّ العطل الذي تحمله كل واحدة
- اقرأ مقاييس ElastiCache في CloudWatch لتختار بين التوسيع الرأسي وإضافة نسخ للقراءة وإضافة shards
صفحة الكتالوج لديك تشغّل استعلاماً واحداً يستغرق 300 مللي ثانية ويعيد الصفوف المئتين نفسها لكل زائر. وعند 400 طلب في الثانية، تشغّل قاعدة البيانات ذلك الاستعلام 400 مرة في الثانية وتحرق معالجها لتنتج جواباً أنتجته قبل قليل. لا عيب في الاستعلام. المشكلة أنك تعيد حساب ثابت، والعلاج أن تحفظ الجواب في مكان أقرب من الشيء الذي حسبه.
وتعطيك AWS مكانين مختلفين تماماً لحفظه، ويسمّي Skill 2.1.2 في اختبار SOA-C03 كليهما: Amazon CloudFront عند الحافة، وAmazon ElastiCache بجوار تطبيقك. وهما ليسا بديلين عن بعضهما، واختيار الخطأ منهما يعني إضافة خدمة لا ترفع حملاً.
ذاكرتان، ومسافتان
| CloudFront | ElastiCache | |
|---|---|---|
| أين تجلس | مواقع الحافة في 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 TTL | Max TTL | Default TTL | محتوى مفتاح التخزين |
|---|---|---|---|---|
CachingOptimized | ثانية واحدة | 31,536,000 ثانية (365 يوماً) | 86,400 ثانية (24 ساعة) | لا شيء سوى ترويسة Accept-Encoding المُطبَّعة |
CachingDisabled | 0 | 0 | 0 | لا شيء |
فـ CachingOptimized هي الجواب الافتراضي للأصول الساكنة. وCachingDisabled هي الجواب الصحيح لأي محتوى شخصي فعلاً، وهي تعمل لأن قيم TTL الثلاث أصفار، لا لأنها لا تمرّر شيئاً.
وثلاثة أخطاء في مفتاح التخزين تكلّف نسبة إصابة حقيقية، ولكل منها علاج:
- حالة الأحرف وترتيبها في سلاسل الاستعلام.
?parameter1=Aو?parameter1=aمفتاحان مختلفان، وكذلك?parameter1=a¶meter2=bو?parameter2=b¶meter1=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=10 | 60 ثانية | تحت الحدّ الأدنى فتُرفَع إلى Minimum TTL |
Cache-Control: max-age=7200 | 7200 ثانية | داخل المدى فتُحترَم كما هي |
Cache-Control: max-age=31536000 | 86400 ثانية | فوق الحدّ الأقصى فتُخفَض إلى Maximum TTL |
لا ترويسة Cache-Control | 3600 ثانية | لم يُقترَح شيء فتنطبق 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. والمحرّك الذي تختاره يحدّد أي إجراءات التوسيع موجودة أصلاً، فهذه ليست مسألة تفضيل.
| Memcached | Valkey / 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 المتكرّرة مكانها الحافة، ونتائج الاستعلامات المتكرّرة مكانها الذاكرة بجوار التطبيق، وما يتغيّر فعلاً مع كل طلب لا مكان له إلا قاعدة البيانات. وهذه الفئة الأخيرة هي ما لا ينقذه التخزين المؤقت، ومنها يبدأ الدرس التالي، على توسيع قاعدة البيانات العلائقية نفسها.
