# قابلية التوسع وتوزيع الأحمال — تطبيق يستقبل 15,000 req/s على غيمة ## 1. Architecture Diagram ![Architecture Diagram](architecture-diagram.svg) ### شرح طبقات المعمارية | الطبقة | الدور | |---|---| | **CDN / DNS** (اختياري) | تقديم الملفات الثابتة (صور، CSS، JS) بعيدًا عن السيرفرات الأساسية لتخفيف الحمل | | **Ghaymah Load Balancer** | يوزّع الـ 15,000 request/second على كل الحاويات المتاحة، ويستبعد أي حاوية فشلت في فحص `/health` | | **طبقة التطبيق (Auto-scaling Group)** | 39 حاوية تشغّل نفس كود التطبيق، بدون حالة (Stateless) — أي حاوية تقدر تخدم أي طلب | | **Valkey (Cache)** | يخزّن نتائج متكررة الطلب في الذاكرة لتقليل عدد المرات اللي التطبيق محتاج فيها يرجع لقاعدة البيانات | | **PgBouncer (Connection Pooler)** | يمنع كل حاوية من فتح اتصال منفصل بقاعدة البيانات — بيجمع الاتصالات في مجموعة مشتركة محدودة | | **PostgreSQL + Ghaymah Block Storage** | قاعدة البيانات الوحيدة اللي عندها حالة (Stateful) — بياناتها محفوظة على Block Storage مستقل عن دورة حياة الحاوية | | **Grafana / Uptime Kuma** | مراقبة اتجاه الموارد وحالة كل حاوية لحظيًا | **لماذا الحاويات Stateless؟** عشان الـ Load Balancer يقدر يوزّع أي طلب على أي حاوية من غير ما يهتم "مين خدم الطلب اللي فات" — أي بيانات محتاجة تتحفظ (Session, User data) بتتخزن في Valkey أو PostgreSQL مش داخل الحاوية نفسها. --- ## 2. حساب عدد الحاويات المطلوبة ### المعطيات - الحمل الكلي: **15,000 request/second** - سعة الحاوية الواحدة: **500 request/second** - هامش أمان: **30%** ### طريقة الحساب الهدف من هامش الأمان هو **عدم تشغيل أي حاوية على أقصى طاقتها بالكامل** — بيسيب مساحة للتعامل مع أي ارتفاع مفاجئ في الحمل (Traffic spike) من غير ما يحصل تعطّل. **الخطوة 1: العدد الأساسي بدون هامش** ``` عدد الحاويات الأساسي = إجمالي الطلبات ÷ سعة الحاوية = 15,000 ÷ 500 = 30 حاوية ``` **الخطوة 2: إضافة هامش الأمان 30%** ``` عدد الحاويات النهائي = 30 × (1 + 0.30) = 30 × 1.30 = 39 حاوية ``` ### ✅ النتيجة: **39 حاوية** > ملاحظة: فيه طريقة حساب بديلة لنفس المفهوم — اعتبار إن كل حاوية بتشتغل بحد أقصى 70% من طاقتها بس (500 × 0.7 = 350 req/s فعّالة)، وده بيدّي `15,000 ÷ 350 ≈ 43` حاوية. الطريقتين صحيحتين مفاهيميًا (الهدف "هامش أمان")، لكن التفسير الأول (39 حاوية) هو الأقرب لصياغة "العدد + هامش 30%" في نص المهمة، فهو المعتمد هنا. ### جدول توضيحي لأحمال مختلفة (Auto-scaling Table) | الحمل الحالي (req/s) | العدد الأساسي | العدد بعد هامش 30% | |---|---|---| | 5,000 | 10 | 13 | | 10,000 | 20 | 26 | | 15,000 | 30 | **39** | | 20,000 | 40 | 52 | هذا الجدول هو أساس سياسة Auto-scaling: كل ما زاد الحمل الفعلي، تزيد المنصة عدد الحاويات تلقائيًا بنفس النسبة. --- ## 3. استراتيجية Cold Start للحاويات الجديدة **المشكلة:** لما Auto-scaling يقرر يضيف حاوية جديدة فجأة (بسبب ارتفاع الحمل)، الحاوية الجديدة بتاخد وقت (Cold Start) قبل ما تبقى جاهزة فعليًا تستقبل طلبات — تحميل التطبيق، فتح اتصالات قاعدة البيانات، تسخين الكاش المحلي... إلخ. لو الـ Load Balancer وجّه طلبات ليها فورًا، ممكن تفشل أو تكون بطيئة جدًا. ### الاستراتيجية المقترحة | # | الإجراء | الفائدة | |---|---|---| | 1 | **Minimum Warm Pool** — حافظ دايمًا على عدد أدنى من الحاويات شغالة (مثلًا 10 حاويات) حتى وقت الحمل المنخفض | يقلل الاعتماد على Cold Start في الحالات العادية؛ التوسع بيبقى غالبًا زيادة على حاويات موجودة أصلًا مش بداية من الصفر | | 2 | **Readiness Probe قبل استقبال الترافيك** | الحاوية الجديدة متتسجلش في الـ Load Balancer إلا بعد ما ترد بنجاح على فحص `/health` أو `/ready` — يمنع توجيه طلبات لحاوية لسه بتبدأ | | 3 | **صور Docker خفيفة ومبنية مسبقًا (Pre-built lightweight images)** | استخدام صور Alpine بدل الصور الكاملة، وبناء الطبقات (Layers) بترتيب يخلي الأجزاء المتغيرة بس تحتاج إعادة تحميل — يقلل وقت الـ pull والبدء | | 4 | **Connection Pool Warm-up عند البدء** | فتح اتصالات قاعدة البيانات (عبر PgBouncer) والكاش (Valkey) فورًا عند إقلاع الحاوية، قبل ما تستقبل أول طلب حقيقي | | 5 | **Predictive/Scheduled Scaling** | لو أوقات الذروة معروفة ومتكررة (مثلًا كل يوم الساعة كذا)، جدولة زيادة الحاويات مسبقًا بدل انتظار الحمل يرتفع فعليًا ثم رد الفعل | | 6 | **Gradual Traffic Ramp-up (Canary-style)** | بعد ما الحاوية الجديدة تجتاز فحص الصحة، وجّهلها نسبة صغيرة من الترافيك الأول (مثلًا 10%) وزوّدها تدريجيًا، بدل ما تستقبل حصتها الكاملة فورًا | | 7 | **Cooldown/Stabilization Window** | فترة انتظار قصيرة (دقيقة أو دقيقتين) بعد كل قرار توسّع قبل اتخاذ قرار توسّع جديد — يمنع "التذبذب" (Flapping) في عدد الحاويات | ### التسلسل الزمني المقترح لإضافة حاوية جديدة ``` قرار Auto-scaling (الحمل تجاوز العتبة) │ ▼ سحب/تشغيل الصورة (Image pull - سريع لو الصورة خفيفة ومخزنة مسبقًا على العقدة) │ ▼ بدء التطبيق + Connection Pool Warm-up │ ▼ Readiness Probe ينجح │ ▼ التسجيل في Load Balancer بحصة ترافيك صغيرة (10%) │ ▼ زيادة الحصة تدريجيًا لحد الوصول لحصة كاملة عادية ``` --- ## 4. استخدام Ghaymah Block Storage للـ Stateful Workloads ### ليه الحاويات نفسها مش مكان مناسب لتخزين البيانات؟ الحاويات في المعمارية دي **Stateless بالتصميم** — ممكن تتقفل أو تتستبدل في أي وقت (Auto-scaling, إعادة نشر, فشل مفاجئ) من غير أي تحذير. أي بيانات محفوظة جوه الحاوية نفسها بتضيع فور ما الحاوية تتقفل. ### دور Block Storage **Ghaymah Block Storage** هو تخزين دائم (Persistent Volume) مستقل تمامًا عن دورة حياة أي حاوية معينة — بيتحط (Attach) على الحاوية اللي محتاجاه، ولو الحاوية دي اتقفلت أو اتستبدلت، الـ Volume بياناته باقية وممكن يتحط على حاوية جديدة. ### أهم الاستخدامات في هذه المعمارية | الحالة | استخدام Block Storage | |---|---| | **قاعدة بيانات PostgreSQL** | ملفات البيانات (Data directory) بتتخزن على Volume منفصل، مش داخل حاوية قاعدة البيانات نفسها — لو الحاوية اتعاد تشغيلها أو اتستبدلت (تحديث نسخة مثلًا)، البيانات باقية | | **رفع ملفات المستخدمين (User uploads)** | لو التطبيق بيسمح برفع صور/ملفات، بتتخزن على Volume مشترك بدل تخزينها محليًا داخل حاوية واحدة (اللي ممكن تختفي وتاخد الملفات معاها) | | **أرشفة اللوجز طويلة المدى** | لوجز التطبيق التفصيلية اللي محتاجة تتحفظ لفترة أطول من عمر الحاوية | | **نسخ احتياطية (Backups)** | تصدير نسخ احتياطية دورية من قاعدة البيانات على Volume منفصل، مستقل عن أي Container instance | ### مبادئ أساسية للاستخدام الصحيح 1. **فصل التخزين عن الحوسبة (Decouple storage from compute):** الحاوية تُعامل كموردة مؤقتة قابلة للاستبدال، والـ Volume هو مكان "الحقيقة الدائمة" للبيانات 2. **حاوية واحدة فعّالة للكتابة في كل مرة** (لقواعد البيانات التقليدية) — لتفادي تعارض الكتابة المتزامنة على نفس الـ Volume 3. **نسخ احتياطي دوري للـ Volume نفسه**، مش الاعتماد عليه كمصدر وحيد للبيانات 4. **قياس الأداء (IOPS/Throughput)** المطلوب حسب حمل قاعدة البيانات — قواعد البيانات عالية الكتابة محتاجة Block Storage بأداء أعلى من مجرد تخزين ملفات ثابتة ### كيف يتكامل هذا مع باقي المعمارية ``` حاوية PostgreSQL (Stateless container, قابلة للاستبدال) │ ▼ (attach) Ghaymah Block Storage Volume (البيانات الفعلية، دائمة) ``` لو حاوية قاعدة البيانات احتاجت تُعاد تشغيلها (تحديث، صيانة، أو حتى فشل)، حاوية جديدة تتشغل وتاخد نفس الـ Volume المرفق — البيانات ما بتضيعش، وده الفرق الجوهري بين التعامل مع الـ 39 حاوية التطبيقية (Stateless، ممكن تتستبدل بحرية) وحاوية قاعدة البيانات (Stateful، البيانات لازم تتحفظ عبر Block Storage).