التوسّع وموازنة الأحمال

مشروع: توسيع نظام من مستخدم واحد إلى مليون مستخدم

18 دقيقة الدرس 10 من 10

مشروع: توسيع نظام من مستخدم واحد إلى مليون مستخدم

يجمع هذا الدرس الختامي كل المفاهيم التي تناولناها خلال هذا الفصل الدراسي. بدلًا من دراسة التقنيات بمعزل عن بعضها، سنتابع الدورة الحياتية الكاملة لمنتج حقيقي — خدمة حفظ المواقع الاجتماعية حيث يحفظ المستخدمون الروابط ويصنّفونها ويشاركونها — ونطور بنيتها التقنية مرحلةً تلو الأخرى مع نمو قاعدة المستخدمين من مطوّر واحد يشغّلها على حاسوبه المحمول إلى مليون مستخدم نشط يولّدون ملايين الطلبات يوميًا.

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

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

المرحلة 1 — خادم واحد (من 0 إلى نحو 1,000 مستخدم)

تطلق الخدمة. تعيش كل البنية التقنية على آلة افتراضية واحدة: خادم Nginx وعملية تطبيق (PHP/Node/Python) وقاعدة بيانات Postgres — جميعها على مثيل t3.medium واحد (معالجان افتراضيان و4 جيجابايت رام). يتعامل هذا الخادم بيسر مع نحو 50–200 طلب في الثانية.

ما يعمل: نشر بالغ البساطة، تكلفة زهيدة (~30 دولار شهريًا)، لا حمل تشغيلي. تستطيع التكرار بسرعة لعدم وجود تنسيق مطلوب.

ما ينكسر أولًا: قاعدة البيانات وخادم الويب يتنافسان على نفس المعالج والذاكرة. استعلام بطيء يُجوّع عملية الويب. ارتفاع مفاجئ في الحركة يستنزف الذاكرة ويُوقف كل شيء. ثمة نقطة فشل واحدة — عطل في العتاد يعني توقف الخدمة بالكامل.

Stage 1 — Single Server Architecture Browser ~1 k users Single VM (t3.medium) Nginx web server App Process business logic Postgres same machine Single Point of Failure everything on one machine
المرحلة 1: كل البنية التقنية على آلة افتراضية واحدة — بسيطة ورخيصة وهشّة.

المرحلة 2 — فصل قاعدة البيانات (من 1,000 إلى نحو 10,000 مستخدم)

الاختناق المُحدَّد: التطبيق وقاعدة البيانات يتنافسان على موارد المضيف ذاته. ارتفاع مفاجئ في عمليات الكتابة يُبطئ تنفيذ الاستعلامات مما يُبطئ عرض الصفحات.

الحل: نقل Postgres إلى آلة افتراضية مخصصة لقاعدة البيانات (مثل r6i.2xlarge — محسّنة للذاكرة). تتفرّغ آلة التطبيق الآن لمعالجة طلبات HTTP فحسب. يسير الاتصال من التطبيق إلى قاعدة البيانات عبر شبكة VPC الخاصة — زمن استجابة منخفض، لا تعرّض للإنترنت.

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

المقايضة: أصبح لديك الآن آلتان تحتاجان إلى تشغيل ومراقبة. قاعدة البيانات لا تزال نقطة فشل واحدة — عطل عتادي في آلة DB يُوقف الخدمة بأكملها.

صحّح الحجم مبكرًا: عند فصل قاعدة البيانات، فعّل فورًا النسخ الاحتياطي الآلي واسترداد النقطة الزمنية على خدمة قاعدة بياناتك المُدارة (مثل AWS RDS). التكلفة ضئيلة والحماية هائلة. فقدان قاعدة البيانات يعني فقدان المنتج بأكمله.

المرحلة 3 — إضافة طبقة كاش (من 10,000 إلى نحو 100,000 مستخدم)

الاختناق المُحدَّد: حركة القراءة تضرب قاعدة البيانات بلا رحمة. تُحمَّل قائمة إشارات مرجعية مستخدم شهير من Postgres آلاف المرات في الدقيقة — نفس الصفوف، مرارًا وتكرارًا.

الحل: نشر مجموعة Redis بين خوادم التطبيق وقاعدة البيانات. تُخزَّن نتائج الاستعلامات المكلفة أو كثيرة التكرار في الكاش. تنفيذ cache-aside: عند فقدان الكاش، يقرأ التطبيق من قاعدة البيانات ويكتب النتيجة في Redis مع وقت انتهاء صلاحية TTL (مثل 60 ثانية)، وتصطدم القراءات اللاحقة بـ Redis خلال تلك النافذة.

الكسب: يمتص كاش مُضبَّط جيدًا 80–95% من حركة القراءة. يتراجع الحمل الاستعلامي على قاعدة البيانات بشكل دراماتيكي، وينخفض زمن الاستجابة من نحو 20 مللي ثانية (رحلة قاعدة بيانات) إلى نحو 1 مللي ثانية (Redis).

المقايضة: إبطال الكاش من أصعب المسائل في علوم الحاسوب. البيانات القديمة باتت ممكنة — مستخدم يحدّث ملفه الشخصي ويراه مستخدم آخر بالشكل القديم 60 ثانية. اختر قيم TTL وإستراتيجيات الإبطال بعناية بناءً على متطلبات الحداثة لكل نوع بيانات.

المرحلة 4 — خوادم تطبيق أفقية وموازن تحميل (من 100,000 إلى نحو 500,000 مستخدم)

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

الحل: التأكد من أن التطبيق عديم الحالة تمامًا (الجلسات في Redis، لا كتابة ملفات محلية)، ثم تشغيل خادمين أو ثلاثة خوادم تطبيق متطابقة خلف موازن تحميل. استخدام خوارزمية round-robin أو least-connections. تفعيل فحوصات الصحة حتى يتجاوز موازن التحميل المثيل الفاشل تلقائيًا.

الكسب: التوسع الأفقي لطبقة التطبيق شبه خطي — 3 خوادم تتعامل مع نحو 3 أضعاف الإنتاجية. تصبح عمليات النشر بدون توقف: أخرج خادمًا من الدوران، حدّثه، أعده، كرّر.

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

Stage 4 — Load-Balanced App Tier with Cache and Separate DB Internet / Users Load Balancer round-robin · health checks App Server 1 stateless App Server 2 stateless App Server 3 stateless Redis Cache sessions + query cache Postgres (dedicated VM) primary — writes + cache misses
المرحلة 4: خوادم تطبيق عديمة الحالة خلف موازن تحميل، طبقة Redis، وخادم Postgres مخصص.

المرحلة 5 — نسخ القراءة لاستعلامات قاعدة البيانات (من 500,000 إلى نحو مليون مستخدم)

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

الحل: إضافة نسختين للقراءة من Postgres. توجيه كل استعلامات القراءة (SELECT) إلى النسخ عبر موجّه قراءة خفيف الوزن. إبقاء كل عمليات الكتابة (INSERT, UPDATE, DELETE) على الخادم الأساسي. هذا هو النسخ الأساسي-الفرعي — يبث الخادم الأساسي سجل الكتابة المسبقة (WAL) إلى النسخ التي تطبّقه بشكل غير متزامن.

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

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

المرحلة 6 — CDN للأصول الثابتة والتخزين المؤقت في الحافة

الاختناق المُحدَّد: نحو 60% من استجابات HTTP هي أصول ثابتة — حزم JavaScript وCSS وصور وأيقونات — لا تتغير بين الطلبات. تخدمها خوادم التطبيق مستهلكةً نطاقًا ترددية ودورات معالج مخصصة للطلبات الديناميكية.

الحل: وضع CDN (CloudFront أو Fastly أو Cloudflare) أمام التطبيق بأكمله. تُخزَّن الأصول الثابتة في عقد CDN الحافية حول العالم. ضبط رؤوس Cache-Control بعناية: max-age=31536000, immutable للأصول ذات الإصدارات، وno-store لاستجابات API المصادق عليها.

الكسب: تُسلَّم الأصول الثابتة من عقدة حافية على بُعد أجزاء من الثانية من المستخدم بصرف النظر عن موقع خوادم الأصل. ينخفض حمل خادم الأصل 60–80%. يحظى المستخدمون في المناطق البعيدة (جنوب شرق آسيا وأفريقيا) بأوقات تحميل سريعة مثل المستخدمين القريبين من مركز البيانات.

تسميم كاش CDN: لا تخدّم أبدًا محتوى مصادقًا عليه أو خاصًا بالمستخدم عبر كاش CDN دون تحديد نطاق مفاتيح الكاش حسب رأس Authorization أو ملف تعريف الارتباط. قد يؤدي CDN مُهيَّأ بشكل خاطئ إلى خدمة بيانات خاصة لمستخدم إلى مستخدم آخر — وهو خرق أمني خطير. أضف دائمًا Vary: Cookie أو استخدم فضاء أسماء URL مستقلًا لمسارات API المصادق عليها.

المرحلة 7 — طابور الرسائل للعمل غير المتزامن

الاختناق المُحدَّد: عمليات معيّنة — إرسال تأكيدات البريد الإلكتروني، وتوليد معاينات الروابط، وإعادة حساب ترتيب شعبية الوسوم — متزامنة وبطيئة. تعيق خيط استجابة HTTP لثوانٍ مما يُدهور الأداء المُدرَك ويهدر موارد خوادم التطبيق الثمينة في عمل غير تفاعلي.

الحل: تقديم طابور رسائل (RabbitMQ أو SQS). عندما يحفظ مستخدم إشارة مرجعية، يضع مُعالج HTTP رسالة مهمة في الطابور ويعيد 202 Accepted فورًا. مجموعة منفصلة من عمليات العامل تستهلك الطابور وتنفّذ العمل البطيء (جلب بيانات الصفحة، إرسال بريد التأكيد) وتعيد المحاولة عند الفشل. تتوسع طبقة الويب وطبقة العمّال باستقلالية تامة.

الكسب: ينخفض زمن استجابة HTTP من 2–3 ثوانٍ إلى 50 مللي ثانية. الأعطال العابرة في الخدمات المجاورة (مزود البريد الإلكتروني متوقف) لا تُسبّب أخطاء HTTP للمستخدمين — تُصفَّف المهام في الطابور وتُعاد محاولتها. يمكن توسيع العمّال باستقلالية عن خوادم الويب بناءً على عمق الطابور.

المرحلة 8 — مجموعات التوسع الآلي والمرونة

الاختناق المُحدَّد: الحركة غير منتظمة. للخدمة ذرى حادة صباح أيام الأسبوع (المستخدمون يلحقون بالروابط المحفوظة) وقيعان في الرابعة فجرًا. تشغيل ما يكفي من الخوادم لتحمّل الذروة على مدار الساعة مُضيّع ومكلف.

الحل: تحويل أسطول خوادم التطبيق إلى مجموعة توسع آلي (ASG). تحديد الحد الأدنى (2) والمطلوب (4) والأقصى (12) من عدد المثيلات. ضبط مُشغّلات التوسع للخارج (إضافة مثيلَين حين يتجاوز متوسط CPU 70% لمدة دقيقتين) ومُشغّلات التقليص للداخل (إزالة مثيل حين ينخفض متوسط CPU دون 30% لمدة 10 دقائق). استخدام التوسع التنبؤي للتسخين المسبق للطاقة قبل فترات الذروة المعروفة.

الكسب: تنخفض تكلفة البنية التحتية 40–60% لأنك تدفع فقط للطاقة التي تحتاجها فعلًا. يمتص النظام تلقائيًا ارتفاعات الحركة الفيروسية المفاجئة دون تدخل بشري.

المقايضة: تستغرق المثيلات الجديدة 2–4 دقائق للتهيئة (تثبيت الحزم، تسخين JVM، بناء كاشات الكود). إذا كانت الارتفاعة مفاجئة، قد تعاني من تدهور في الأداء خلال فترة الإحماء. تخفّف من ذلك بصور آلة مُعدَّة مسبقًا (AMIs مع التطبيق مثبّتًا مسبقًا) لتقليص وقت التشغيل إلى 30–60 ثانية.

البنية الكاملة لمليون مستخدم في لمحة

Full Architecture at 1 Million Users Users (~1M) CDN (Cloudflare) static assets · edge cache Load Balancer (HA) health checks · TLS termination App Server (x2-12) Auto Scaling Group App Server (x2-12) Auto Scaling Group Worker Pool async job consumers Message Queue SQS / RabbitMQ Redis Cache Cluster sessions · query results Postgres Primary writes + cache misses Read Replica 1 Read Replica 2
البنية الكاملة لمليون مستخدم: CDN وموازن تحميل عالي التوافر وطبقة تطبيق ذاتية التوسع وكاش Redis وطابور رسائل وخادم Postgres أساسي مع نسختين للقراءة.

ورقة غش قرارات التوسع

كل تطور معماري اتبع النمط ذاته: قِس، حدّد الاختناق، طبّق أدنى تغيير يحلّه، قِس من جديد. هذا مرجع مكثّف:

  • معالج خادم التطبيق مُشبَع؟ أضف خوادم تطبيق أكثر خلف موازن التحميل (توسع أفقي). تأكد من اللاحالة أولًا.
  • حركة القراءة على قاعدة البيانات مرتفعة؟ أضف كاش Redis. إذا كانت نسبة إصابة الكاش مرتفعة بالفعل، أضف نسخ القراءة.
  • عمليات الكتابة على قاعدة البيانات هي الاختناق؟ وسّع الخادم الأساسي رأسيًا. إذا بلغت السقف، فكّر في التجزئة (sharding) حسب معرّف المستخدم أو المستأجر.
  • الأصول الثابتة تستهلك نطاق الأصل الترددي؟ أضف CDN. ضبط رؤوس الكاش الجريئة على الأصول الثابتة.
  • المهام المتزامنة البطيئة تُدهور زمن استجابة HTTP؟ ادفعها إلى طابور رسائل وعالجها بشكل غير متزامن.
  • الحركة متفاوتة ولا يمكن التنبؤ بها؟ ضبط التوسع الآلي مع صور آلة جاهزة مسبقًا لأوقات تشغيل سريعة.
  • مستخدمون عالميون يعانون من زمن استجابة مرتفع؟ نشر متعدد المناطق مع اعتبارات إقامة البيانات، أو توازن تحميل عالمي، أو توجيه Geo-DNS.
قابلية المراقبة ليست اختيارية: كل قرار توسع في هذا الدرس نشأ عن اختناق مُقاس. لا تستطيع إجراء تلك القياسات بدون قابلية مراقبة صحيحة: مقاييس (CPU، ذاكرة، معدل الطلبات، معدل الأخطاء، مئينيات زمن الاستجابة)، وتتبّع موزّع، وسجلات مركزية. قبل أن تتوسع، راقب. أنت تطير بدون أجهزة قياس في غياب ذلك.

ماذا يأتي بعد مليون مستخدم؟

الوصول إلى مليون مستخدم بالبنية أعلاه إنجاز كبير، لكن الرحلة لا تنتهي هنا. عند عشرات أو مئات الملايين من المستخدمين، تبرز التحديات التالية: تجزئة قاعدة البيانات لتوزيع حمل الكتابة عبر أقسام، والنشر متعدد المناطق لتحمّل انقطاع مركز البيانات، وتحليل الخدمات (الخدمات المصغّرة) لتمكين الفرق من النشر باستقلالية، والبنى المدفوعة بالأحداث لفصل المكونات على نطاق واسع. هذه مواضيع متقدمة — لكن بتطبيق النهج التدريجي المدفوع بالأدلة الذي تدرّبت عليه هنا، يمكن معالجة كل منها بشكل منهجي، اختناقٌ تلو الآخر.

اكتمل الفصل الدراسي: يختتم هذا الدرس فصل التوسع وموازنة التحميل. لقد تناولت الطيف الكامل — من الفرق النظري بين التوسع الرأسي والأفقي، مرورًا بخوارزميات موازنة التحميل وفحوصات الصحة، وصولًا إلى تطور بنية معمارية عملية من 8 مراحل. المهارة الأهم التي بنيتها ليست أي تقنية بعينها، بل العادة المنضبطة في تحديد الاختناقات قبل إضافة التعقيد.

اكتمل الدرس!

تهانينا! لقد أكملت جميع الدروس في هذا البرنامج التعليمي.