moncef
كل الكتابات

·7 دقائق قراءةbackendarchitecture

ليلة عدل 3، و كيفاش نبني فورم يسجل فيه مليون انسان في نفس الوقت

وش اللي يصير بالضبط كي البلاد كاملة تدخل لنفس الفورم في نفس الدقيقة، و كيفاش يقدر المطور يتفادى هذا في المنصة تاعو.

المحتويات11 قسما

يوم الجمعة 5 جويلية 2024 على الساعة التاسعة مساءً فتح التسجيل في عدل 3. اذا كنت جزائري أكيد تتفكر هاذيك الليلة، أو على الأقل واحد من عائلتك يتفكرها: رسائل خطأ، صفحات ما تتحلش، ناس قاعدين يحاولو حتى الساعة 3 تع الصباح، و طوابير قدام السيبرات.

على الساعة العاشرة صباحاً كان عدد المسجلين 358,000، و بعد 14 يوم وصل لأكثر من مليون و 400 ألف.1 الأرقام من Algérie Eco (6 و 8 جويلية 2024) و Algérie360 (جانفي 2025). الوزارة قالت أن البطء سببه العدد الكبير من الناس اللي حاولو يسجلو في نفس الوقت، و أنها صدّت عشرات محاولات القرصنة.

طبعا أنا ما نعرفش كيفاش المنصة مبنية من الداخل، و هدفي مش اني نلوم حتى واحد. لكن هذا مشكل رح يواجهه كل مطور جزائري يعمل على موقع حكومي في يوم من الأيام: فورم يحتاجه الناس كامل في نفس الليلة، نتائج الباك أو التوجيه الجامعي. فحبيت نجاوب على سؤال بسيط: لوكان سيلانس هي اللي تبني في هاذي المنصة، وش كانت رح تدير؟

8 تسجيلات في الثانية فقط؟#

358,000 تسجيل في حوالي 13 ساعة، معناه تقريبا 8 تسجيلات في الثانية.

طبعا الانسان اللي رح يقرأ هذا الرقم رح يقول: هذا رقم صغير، أي قاعدة بيانات عادية تقدر تكتب آلاف الأسطر في الثانية. و هنا بالضبط الفكرة، المشكل ما كانش في حجم البيانات، المشكل في طريقة وصول الزوار: مئات الآلاف من الناس في نفس الدقيقة، و كل واحد يستنى شوي و يدير refresh، يعني request جديد يزيد على سيرفر اللي أصلا ما قدرش يجاوب على اللي قبلو.

هذا علاش ما نبنوش على عدد الناس اللي رح تدخل للمنصة، نبنو على الذروة و على المحاولات المتكررة اللي تجي معاها.

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

registration.nightعرض تفاعلي
الحلول
الضغط على السيرفر×100×10قدرة السيرفر
سجلوحابين يسجلو
21:0000:0003:0006:0009:00
سجلو قبل العاشرة
358,426
رسائل خطأ
22.6 مليون
refresh
15.2 مليون
تسجيلات مكررة
190,793

نموذج مضبوط باش الليلة بلا حلول تكمل قريب من 358,000 الحقيقية. أكبر ضغط ×68.0

شعّل الحلول و عاود نفس الليلة على نفس السيرفر.

و البيانات؟ تبقى في الجزائر#

منصة كيما هاذي فيها رقم التعريف، رقم الهاتف، و الوضعية العائلية تاع ملايين المواطنين. حسب القانون 18-07 (اللي تعدّل بالقانون 25-11 في 2025)، نقل المعطيات الشخصية للخارج يحتاج ترخيص من السلطة الوطنية، و ممنوع اذا كان يمس بالأمن العمومي.2 ملخص القانون و تعديلاتو عند DLA Piper. بالنسبة لمنصة عمومية الجواب واضح: الـ backend و البيانات يبقاو على سيرفرات في الجزائر.

لهذا الـ backend و البيانات يخدمو بأدوات open source على السيرفرات تاعك في data center جزائري. الشيء الوحيد اللي يقدر يكون برا هو صفحة الـ HTML، لأن ما فيها حتى معلومة شخصية.

الصفحة متحتاجش سيرفر#

صفحة الفورم هي نفسها لكل الناس، فما كاين حتى سبب يخلي التطبيق يبنيها من جديد لكل زائر.

ابنيها صفحة HTML ثابتة (static) و حطها على CDN كيما Cloudflare. الصفحة ما فيها حتى معلومة شخصية، و هكذا مليون زيارة تتجاوب من أقرب نقطة للزائر و ما توصلش أصلا للسيرفرات تاعك، السيرفر يستقبل فقط الطلب المهم: الإرسال.

تنبيه

انتبه لنقطة مهمة

الإرسال لازم يروح مباشرة للـ backend في الجزائر، على subdomain منفصل (كيما api)، ماشي عن طريق Cloudflare، باش المعلومات الشخصية ما تعديش على سيرفرات برا. و اذا ما حبيتش حتى الصفحة تكون برا، Nginx أو Varnish في الـ data center يديرو نفس الخدمة.

مخطط تدفق: الصفحة برا، البيانات في الجزائر⁧الجزائر⁩⁧برا⁩⁧يحل الصفحة⁩⁧الإرسال، مباشرة⁩⁧سطر واحد⁩⁧مهمة⁩⁧الخدمة الثقيلة⁩⁧الزائر⁩⁨Cloudflare CDN⁩⁧صفحة HTML ثابتة⁩⁨api⁩⁧subdomain منفصل⁩⁨queue⁩⁨workers⁩⁧قاعدة البيانات⁩
الصفحة برا، البيانات في الجزائر

مخطط تدفق، عدد أجزائه 6.

  • الزائر، شخص
  • Cloudflare CDN (صفحة HTML ثابتة)، خدمة خارجية
  • api (subdomain منفصل)، مميز
  • queue
  • workers
  • قاعدة البيانات، قاعدة بيانات

داخل الجزائر: api، queue، workers، قاعدة البيانات.

داخل برا: Cloudflare CDN.

الروابط:

  • من الزائر إلى Cloudflare CDN: يحل الصفحة
  • من الزائر إلى api: الإرسال، مباشرة (المسار المميز)
  • من api إلى قاعدة البيانات: سطر واحد (المسار المميز)
  • من api إلى queue: مهمة
  • من queue إلى workers
  • من workers إلى قاعدة البيانات: الخدمة الثقيلة (المسار المميز)

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

استقبل الطلب و خليه يطبخ على نار هادئة#

أغلب الفورمات تدير كل شيء في request واحد: تتحقق من رقم التعريف، تتحقق من الشروط، تكتب في عدة جداول، تبعث SMS، و من بعد تعرض التأكيد. و كل خطوة من هاذي الخطوات تخلي الاتصال مفتوح، و الطابور اللي وراه يكبر.

الحل اننا نقسموها على جزئين:

  1. نقطة الإرسال: تتحقق من المعلومات، تكتب سطر واحد، و ترجع للمستخدم رقم مرجعي مباشرة.
  2. الـ workers: يقراو من queue بالسرعة اللي يقدر عليها النظام، و يديرو الخدمة الثقيلة (الشروط، التكرار، الـ SMS).
مخطط تسلسل: الجواب في ثانية، و الخدمة من بعد01 ⁨POST /register⁩02 ⁧سطر واحد⁩03 ⁧مهمة جديدة⁩04 ⁧202، رقمك 4821⁩⁧المستخدم عندو رقمو، و الخدمة تكمل في الخلفية⁩05 ⁧ياخذ مهمة⁩06 ⁧الشروط و التكرار⁩⁧بالوتيرة اللي يقدر عليها النظام⁩07 ⁧التأكيد⁩08 ⁧رسالة⁩⁧المستخدم⁩⁨api⁩⁧قاعدة البيانات⁩⁨queue⁩⁨worker⁩⁨SMS⁩
الجواب في ثانية، و الخدمة من بعد

تسلسل بين المستخدم، شخص؛ api، مميز؛ قاعدة البيانات، قاعدة بيانات؛ queue؛ worker؛ SMS، خدمة خارجية.

  1. من المستخدم إلى api: POST /register
  2. من api إلى قاعدة البيانات: سطر واحد (المسار المميز)
  3. من api إلى queue: مهمة جديدة
  4. من api إلى المستخدم: 202، رقمك 4821 (رد)
  5. المستخدم عندو رقمو، و الخدمة تكمل في الخلفية
  6. من worker إلى queue: ياخذ مهمة
  7. من worker إلى قاعدة البيانات: الشروط و التكرار (المسار المميز)
  8. ملاحظة على worker وقاعدة البيانات: بالوتيرة اللي يقدر عليها النظام
  9. من worker إلى SMS: التأكيد
  10. من SMS إلى المستخدم: رسالة (رد)

الـ worker هنا هو برنامج منفصل يخدم في الخلفية على السيرفرات تاعك، و ما عندو حتى علاقة مباشرة بالمستخدم. ياخذ مهمة وحدة من الـ queue، يكملها، و من بعد ياخذ اللي بعدها. الـ queue تقدر تكون RabbitMQ أو Redis، و الزوج open source. اذا وصلو مليون طلب في ساعة، الـ queue تحفظهم، و الـ workers يكملوهم في ساعات قليلة بنفس الوتيرة، و قاعدة البيانات ما تحسش بالضغط.

المستخدم يوصلو "استلمنا طلبك، رقمك 4821" في أقل من ثانية، و الخدمة الثقيلة تكمل في الدقائق اللي بعدها، بدون ما يقعد حتى واحد يستنى قدام شاشة التحميل.

ضغطت على submit خمس مرات؟ ميتبدل حتى شيء#

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

جرب بنفسك: اضغط على سجّل كيما كانو الناس يضغطو هاذيك الليلة، و شوف وش يصير في الجدول:

submit.twiceعرض تفاعلي
رقم التعريف1099 4041 2033 5700 14
السيرفر

بلا key كل request يكتب سطر4 سطور

  1. POST /register4821
  2. POST /register4822
  3. POST /register4823
  4. POST /register4824
48215700 14
48225700 14
48235700 14
48245700 14

رقمك 4824

4 سطور لنفس الشخص

مع idempotency نفس الرقم، نفس الجواب1 سطور

  1. POST /register4821
  2. POST /register4821
  3. POST /register4821
  4. POST /register4821
48215700 14

رقمك 4821

أربع ضغطات على سيرفر بطيء. دورك: اضغط سجّل.

اضغط سجّل كم من مرة: بلا key كل ضغطة سطر جديد، و مع رقم التعريف كـ key يبقى سطر واحد.

الحل اسمو idempotency، يعني كل إرسال عندو مفتاح، و هنا المفتاح الطبيعي هو رقم التعريف الوطني. اذا جا نفس المفتاح مرة ثانية، النظام يرجع النتيجة الأولى بدون ما يعاود الخدمة، و مع unique index قاعدة البيانات تدير الجزء الصعب عليك:

register.tsTypeScript
// national_id has a UNIQUE index, so a second submit inserts nothing
const row = await db
  .insert(registrations)
  .values({ nationalId, phone, wilaya })
  .onConflictDoNothing({ target: registrations.nationalId })
  .returning({ ref: registrations.ref });

const ref = row[0]?.ref ?? (await findRef(nationalId));
return c.json({ ref }, 202);

و هذا يجاوب حتى على السؤال اللي كان في كل قروبات الفيسبوك هاذيك الليلة: "سجلت و لا لا؟"، عاود ابعث و النظام يقولك.

الطابور خير من الكراش#

اذا كنت تعرف أن مليون انسان رح يدخلو على التاسعة مساءً، تقدر تقرر قداش واحد يدخل في نفس الوقت.

الـ waiting room هي خدمة صغيرة تستضيفها عندك قدام التطبيق، تعطي لكل زائر تذكرة فيها رقمه، و تدخّل فقط العدد اللي يقدر عليه السيرفر، و الباقي يشوفو دورهم في الطابور. طبعا الطابور يقلق شوي، لكن على الأقل واضح و عادل: الناس تدخل بالترتيب، مش حسب شكون دار refresh في اللحظة المناسبة.

البوتات تسجل قبلك#

فورم عام فيه قاعدة "اللي يسجل الأول يتخدم الأول" يجذب السكريبتات، و كاين حتى extension على Chrome Web Store مصنوع خصيصا باش يعمر فورم عدل 3 أوتوماتيكيا، يعني كل request أوتوماتيكي ياخذ دور انسان حقيقي.

  • rate limiting من البداية: حسب الـ IP و حسب رقم التعريف، في Nginx، قبل ما يوصل الطلب للتطبيق.
  • challenge عند الإرسال: واحد تستضيفو عندك كيما ALTCHA (proof of work، بدون خدمة أجنبية)، في خطوة الإرسال مش عند فتح الصفحة.
  • حماية كود الـ SMS: هاذي النقطة لازم تكون الأكثر تقييدا، لأن كل SMS يتبعث عن طريق المتعامل المحلي بالدراهم.

قاعدة البيانات أول وحدة توقف#

زيادة سيرفرات للتطبيق سهلة، لكن قاعدة البيانات لا. كي تزيد عشرين سيرفر في دقيقة، يفتحو عشرين مرة اتصالات أكثر، و في الأخير قاعدة البيانات هي اللي توقف مش السيرفرات.

  • استعمل connection pooler (كيما PgBouncer) قدام قاعدة البيانات.
  • خلي الكتابة صغيرة: insert واحد، مع index على رقم التعريف.
  • دير cache لكل حاجة تقدر عليها (قائمة الولايات، الشروط، الصفحة نفسها) باش متوصلش أصلا لقاعدة البيانات.

جرب المنصة قبل ما يجربوها الناس#

كل اللي قلناه الفوق ميتأكدش حتى تجربو بزوار حقيقيين. أدوات كيما k6 أو Locust تقدر تحاكي عشرات الآلاف من المستخدمين من لابتوب واحد، و الأفضل تشغلها من داخل الجزائر على الـ data center الحقيقي، لأن الشبكة بين المستخدم و السيرفر حتى هي جزء من الاختبار.

جرب بعشر مرات عدد الزوار اللي تتوقعو، و جرب مع مستخدمين يعاودو المحاولة كي تتأخر الصفحة، لأن هذا اللي يصير في الواقع، صلّح أول حاجة توقف و عاود التجربة.

و لوكان المشكل في القانون مش في السيرفر؟#

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

بعض القرارات البسيطة تقدر تخلي المشكل التقني يختفي:

  • فتح التسجيل حسب الولاية، أو حسب آخر رقم في رقم التعريف، على عدة أيام.
  • ترك التسجيل مفتوح لمدة أسبوعين و من بعد قرعة، باش اللي يسجل الأول مياخذش حتى أفضلية.
  • الإعلان بوضوح أن الترتيب في اليوم الأول ميأثر في حتى شيء.

هاذي نفس المليون و 400 ألف على نفس 14 يوم، بثلاث قواعد مختلفة. بدّل القاعدة و شوف الذروة:

rules.vs.peakعرض تفاعلي
القاعدة
×108 في الثانية، اللي قدر عليه السيرفر الليلة الأولى
اليوم 1اليوم 4اليوم 7اليوم 10اليوم 14
أكبر ساعة
×10.4
ساعات فوق القدرة
4

الكل يتسابق على الدقيقة الأولى.

نفس 1.4 مليون انسان في نفس 14 يوم، بثلاث قواعد.

رأيي

أرخص request هو اللي مكانش لازم يوصل على التاسعة مساءً.

باختصار#

اذا عندك إطلاق قريب و تتوقع ضغط كبير:

  1. الـ backend و البيانات في الجزائر، بأدوات open source.
  2. صفحة static على CDN، و الإرسال مباشرة للـ backend في الجزائر.
  3. كتابة وحدة صغيرة لكل إرسال، و الباقي في queue مع workers في الخلفية.
  4. idempotency على رقم التعريف.
  5. waiting room لساعة الافتتاح.
  6. rate limiting و challenge تستضيفهم عندك.
  7. قاعدة بيانات محمية بـ pooler.
  8. load test بعشر مرات الزوار اللي تتوقعهم، مع إعادة المحاولة.
  9. قواعد توزع الزوار على عدة أيام بدل ما تجمعهم في دقيقة وحدة.

ما كاين حتى حل من هاذي الحلول جديد، هي نفس الأدوات اللي تخلي المتاجر الإلكترونية تخدم في الـ Black Friday، و أغلبها يكلف أقل من ليلة وحدة من الأخبار السيئة.

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

في Selance نبنو منصات للشركات في الجزائر و الخليج، منصات لازم تبقى تخدم حتى في اليوم اللي فيه أكبر ضغط. اذا عندك إطلاق قريب و حاب واحد يراجع معاك الـ architecture، تواصل معايا.

الحواشي

  1. الأرقام من Algérie Eco (6 و 8 جويلية 2024) و Algérie360 (جانفي 2025). ↩︎

  2. ملخص القانون و تعديلاتو عند DLA Piper. ↩︎

هل أفادك؟

مشروعك

تعمل على شيء مشابه؟

إن كان هذا قريبا مما تبنيه، يمكنني مساعدتك. احجز مكالمة ننظر فيه معا، أو راسلني عنه.