# قابلية التوسع وتوزيع الأحمال — تطبيق يستقبل 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).
