11 KiB
قابلية التوسع وتوزيع الأحمال — تطبيق يستقبل 15,000 req/s على غيمة
1. Architecture Diagram
شرح طبقات المعمارية
| الطبقة | الدور |
|---|---|
| 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 |
مبادئ أساسية للاستخدام الصحيح
- فصل التخزين عن الحوسبة (Decouple storage from compute): الحاوية تُعامل كموردة مؤقتة قابلة للاستبدال، والـ Volume هو مكان "الحقيقة الدائمة" للبيانات
- حاوية واحدة فعّالة للكتابة في كل مرة (لقواعد البيانات التقليدية) — لتفادي تعارض الكتابة المتزامنة على نفس الـ Volume
- نسخ احتياطي دوري للـ Volume نفسه، مش الاعتماد عليه كمصدر وحيد للبيانات
- قياس الأداء (IOPS/Throughput) المطلوب حسب حمل قاعدة البيانات — قواعد البيانات عالية الكتابة محتاجة Block Storage بأداء أعلى من مجرد تخزين ملفات ثابتة
كيف يتكامل هذا مع باقي المعمارية
حاوية PostgreSQL (Stateless container, قابلة للاستبدال)
│
▼ (attach)
Ghaymah Block Storage Volume (البيانات الفعلية، دائمة)
لو حاوية قاعدة البيانات احتاجت تُعاد تشغيلها (تحديث، صيانة، أو حتى فشل)، حاوية جديدة تتشغل وتاخد نفس الـ Volume المرفق — البيانات ما بتضيعش، وده الفرق الجوهري بين التعامل مع الـ 39 حاوية التطبيقية (Stateless، ممكن تتستبدل بحرية) وحاوية قاعدة البيانات (Stateful، البيانات لازم تتحفظ عبر Block Storage).