اعتقدت أن تطبيقي صاروخ… حتى سحقه 100 مستخدم: كيف كشف اختبار الحِمل عنق الزجاجة الخفي؟

يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.

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

قررت أعمل إطلاق بسيط ومحدود، نشرت التطبيق في مجموعة لطلاب جامعتي القديمة على فيسبوك. في أول ساعة، كان كل شيء تمام. 10 مستخدمين، 20، 30… والأمور مستقرة. فجأة، بدأت توصلني رسايل: “أبو عمر، التطبيق بطيء جدًا!”، “يا رجل الصفحة الرئيسية مش راضية تفتح!”، “كل ما أضيف مهمة بعلّق!”.

فتحت لوحة المراقبة (Monitoring Dashboard) وإذ بي أرى الكارثة: استهلاك المعالج (CPU) 100%، والذاكرة (RAM) على وشك الامتلاء، وزمن استجابة الطلبات (Response Time) بالثواني وليس بالميللي ثانية. كان عدد المستخدمين النشطين بالكاد تجاوز الـ 100 مستخدم. شعرت بإحباط شديد، كيف لتطبيق سريع جدًا أن ينهار بهذا الشكل؟ هنا كانت الصدمة الأولى، والإدراك بأن “تطبيقي سريع على جهازي” هي واحدة من أكبر الأكاذيب التي نكذبها على أنفسنا كمبرمجين. هذه الحادثة كانت درسي القاسي والمفيد الذي أدخلني بعمق إلى عالم اختبارات الأداء، وتحديدًا “اختبار الحِمل” (Load Testing).

ما هو اختبار الحِمل (Load Testing) وليش هو مهم؟

ببساطة يا جماعة، اختبار الحِمل مش عشان يشوف إذا الكود تبعك شغال أو فيه أخطاء منطقية (Bugs). هداك شغل الـ Unit Tests والـ Integration Tests. اختبار الحِمل بيجاوب على سؤال مختلف تمامًا: “كيف سيتصرف تطبيقي تحت ضغط حقيقي ومتوقع من المستخدمين؟”.

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

“اختبار الحِمل لا يختبر “ماذا” يفعله نظامك، بل يختبر “كيف” يفعله تحت الضغط.”

الهدف هو محاكاة عدد معين من المستخدمين الافتراضيين (Virtual Users) الذين يستخدمون تطبيقك في نفس الوقت، ومراقبة أداء النظام لتحديد أقصى قدرة له قبل أن يبدأ الأداء في التدهور أو الانهيار.

الفرق بين اختبار الحِمل وأنواع اختبارات الأداء الأخرى

  • Load Testing (اختبار الحِمل): يقيس الأداء تحت حِمل “متوقع”. مثلاً، تتوقع 1000 مستخدم متزامن في أوقات الذروة، فتختبر النظام بهذا العدد.
  • Stress Testing (اختبار الإجهاد): يدفع النظام إلى ما بعد طاقته الاستيعابية لتحديد نقطة الانهيار. يعني بدل 1000 مستخدم، بتجرب 2000 أو 3000 عشان تشوف متى وكيف بيفشل النظام.
  • Spike Testing (اختبار الارتفاع المفاجئ): يحاكي زيادة مفاجئة وكبيرة في عدد المستخدمين، مثل وقت إعلان تسويقي كبير، ثم عودة العدد إلى طبيعته.

في قصتي، كنت بحاجة ماسة إلى اختبار الحِمل لأفهم سلوك تطبيقي تحت ضغط 100 مستخدم فقط!

رحلة الكشف عن “عنق الزجاجة” (The Bottleneck)

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

  1. Apache JMeter: أداة قوية جدًا، مفتوحة المصدر، مبنية بالجافا. فيها واجهة رسومية (GUI) بتسهل بناء خطط الاختبار المعقدة. لكنها ممكن تكون ثقيلة على الموارد شوي.
  2. k6 (by Grafana Labs): أداة أحدث، مكتوبة بلغة Go، وتستخدم JavaScript/TypeScript لكتابة سكربتات الاختبار. خفيفة جدًا على الموارد، وموجهة للمطورين (Developer-friendly) لأنها بتعتمد على الكود.

بحكم خلفيتي كمطور وحبي للكود، اخترت أبدأ مع k6 لسهولته وسرعته.

الخطوة الأولى: كتابة سكربت اختبار بسيط بـ k6

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


import http from 'k6/http';
import { sleep } from 'k6';

// هذا هو إعداد الاختبار
export const options = {
  // المراحل: زيادة تدريجية للمستخدمين
  stages: [
    { duration: '30s', target: 50 },   // زيادة إلى 50 مستخدم خلال 30 ثانية
    { duration: '1m', target: 100 },  // زيادة إلى 100 مستخدم خلال دقيقة
    { duration: '30s', target: 0 },    // العودة إلى 0 مستخدم
  ],
  // شروط النجاح: إذا فشل أكثر من 1% من الطلبات، أو كان p95 أعلى من 500ms، اعتبر الاختبار فاشلاً
  thresholds: {
    'http_req_failed': ['rate<0.01'], // معدل الفشل أقل من 1%
    'http_req_duration': ['p(95)<500'], // 95% من الطلبات يجب أن تكون أسرع من 500ms
  },
};

// هذا هو الكود الذي سينفذه كل مستخدم افتراضي
export default function () {
  // طلب GET للصفحة الرئيسية
  http.get('https://api.jidwali.app/tasks');

  // انتظار عشوائي بين 1 و 3 ثواني لمحاكاة سلوك المستخدم
  sleep(Math.random() * 2 + 1); 
}

شرح بسيط للكود:

  • options: هنا نحدد سيناريو الاختبار. في حالتي، طلبت من k6 أن يزيد عدد المستخدمين الافتراضيين (VUs) تدريجيًا إلى 100 مستخدم.
  • thresholds: هذه أهم جزئية. هنا أضع “شروط النجاح”. قلت له إن الاختبار يعتبر فاشلاً إذا كان معدل الطلبات الفاشلة أكثر من 1%، أو إذا كان زمن استجابة 95% من الطلبات (p95) أبطأ من 500 ميللي ثانية.
  • export default function (): هذا هو قلب السكربت. كل مستخدم افتراضي سيقوم بتنفيذ هذا الكود بشكل متكرر: يطلب الصفحة الرئيسية وينتظر قليلاً.

الخطوة الثانية: تشغيل الاختبار ورؤية الحقيقة المرة

شغلت الاختبار، وفي ثوانٍ بدأت الأرقام تظهر على الشاشة. كانت النتائج صادمة ومطابقة تمامًا لما حدث في الواقع:

  • عندما كان عدد المستخدمين 50، كان متوسط زمن الاستجابة حوالي 400ms (مقبول لكن على الحافة).
  • بمجرد أن بدأ العدد يقترب من 100، قفز زمن الاستجابة إلى 3500ms (3.5 ثوانٍ!).
  • معدل الطلبات الفاشلة (http_req_failed) بدأ يرتفع.
  • شرط النجاح p(95)<500 فشل فشلاً ذريعًا.

الآن، أصبح لدي دليل رقمي على المشكلة. لم يعد الأمر مجرد “شعور” بأن التطبيق بطيء، بل أرقام وبيانات تثبت وجود عنق زجاجة حقيقي.

الخطوة الثالثة: تحديد مكان “عنق الزجاجة”

مع وجود البيانات، بدأت رحلة البحث. عنق الزجاجة في تطبيقات الويب غالبًا ما يكون في أحد هذه الأماكن:

  1. قاعدة البيانات (Database): استعلامات بطيئة،缺少 فهارس (indexes)، اتصالات كثيرة.
  2. الكود البرمجي للتطبيق (Application Code): خوارزميات غير فعالة، استدعاءات متكررة للـ API، مشكلة N+1.
  3. البنية التحتية (Infrastructure): سيرفر ضعيف (CPU/RAM)، إعدادات خاطئة لخادم الويب (Nginx/Apache).

بما أن استهلاك المعالج كان 100%، شككت فورًا في قاعدة البيانات. فتحت سجلات الاستعلامات البطيئة (Slow Query Log) في قاعدة بياناتي (كانت PostgreSQL وقتها)، وهنا كانت المفاجأة الكبرى.

وجدت أن استعلامًا واحدًا، مسؤول عن جلب المهام للمستخدم، كان يتكرر مع كل طلب للصفحة الرئيسية، وكان يستغرق وقتًا طويلاً جدًا كلما زاد عدد الصفوف في جدول المهام. المشكلة كانت أنه يقوم بعملية “Full Table Scan”، أي أنه يمر على كل السجلات في الجدول ليبحث عن مهام المستخدم الحالي.

السبب؟ ببساطة، كنت ناسي أضيف فهرس (Index) على عمود user_id في جدول tasks!

الحل كان بسيطًا بشكل يبعث على الضحك (والبكاء بنفس الوقت):


-- إضافة فهرس لعمود `user_id` في جدول `tasks`
CREATE INDEX idx_tasks_user_id ON tasks(user_id);

هذا السطر الواحد يخبر قاعدة البيانات أن تنشئ هيكل بيانات سريع للبحث عن طريق user_id، بدلاً من مسح الجدول بأكمله في كل مرة.

الخطوة الرابعة: إعادة الاختبار والشعور بالنصر 🚀

بعد إضافة الفهرس، قمت بإعادة تشغيل نفس سكربت k6 بالضبط. النتيجة؟ كانت كالليل والنهار.

  • عند 100 مستخدم متزامن، كان متوسط زمن الاستجابة أقل من 80ms.
  • معدل الطلبات الفاشلة: 0%.
  • استهلاك المعالج على السيرفر بالكاد تجاوز 20%.
  • جميع شروط النجاح (Thresholds) تم تحقيقها بنجاح باهر.

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

نصائح أبو عمر الذهبية في اختبارات الأداء

من خلال هذه التجربة وغيرها، تعلمت دروسًا غالية، وأحب أشارككم بعضها:

  1. 💡 ابدأ مبكرًا وبسيطًا: لا تنتظر حتى قبل الإطلاق بأسبوع لتبدأ اختبار الأداء. اكتب سكربت بسيط مثل الذي عرضته واجعله جزءًا من روتينك.
  2. ⚠️ لا تختبر على بيئة الإنتاج (Production) مباشرةً: هذا خطأ قاتل. قم بإنشاء بيئة مطابقة للإنتاج قدر الإمكان (Staging Environment) وقم بإجراء اختباراتك عليها.
  3. 📊 اعرف مقاييسك (Know Your Metrics): ما هو زمن الاستجابة المقبول لتطبيقك؟ 200ms؟ 500ms؟ حدد أهدافك (SLOs – Service Level Objectives) واكتبها في thresholds الخاصة بك.
  4. 🤖 الأتمتة هي المفتاح (Automation is Key): أفضل شيء هو دمج اختبارات الحِمل في مسار الـ CI/CD الخاص بك. مثلاً، يمكنك تشغيل اختبار حِمل صغير كل ليلة على بيئة الـ Staging وإرسال تقرير تلقائي.
  5. 🧠 الأداة مجرد وسيلة: تعلم k6 أو JMeter ممتاز، لكن الأهم هو فهم كيفية تحليل النتائج وربطها بالمشاكل الحقيقية في نظامك. ركز على فهم المقاييس مثل p95, p99, ومعدلات الفشل.

الخلاصة: من مبرمج قلق إلى مهندس واثق ✅

تجربة “جدولي” علمتني أن بناء تطبيق “شغّال” هو نصف الطريق فقط. النصف الآخر، والأكثر أهمية لنجاح أي منتج، هو بناء تطبيق “موثوق” (Reliable) وقادر على الصمود. اختبار الحِمل لم يكن مجرد خطوة تقنية، بل كان نقلة نوعية في طريقة تفكيري، حوّلني من مبرمج يثق بـ “إنها تعمل على جهازي” إلى مهندس يفكر في الضغط، والسعة، وتجربة المستخدم النهائي تحت أي ظرف.

لا تخف من اختبار تطبيقك. بالعكس، كن أنت أول من يكتشف نقاط ضعفه ويقسو عليه في بيئة الاختبار، حتى يكون لطيفًا وسريعًا مع مستخدميك في العالم الحقيقي. هذا هو الفرق بين “شغل نظيف” ومنتج ينهار عند أول اختبار حقيقي.

أتمنى لكم كل التوفيق في مشاريعكم، ولا تنسوا تختبروا أحمالكم!

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

التكنلوجيا المالية Fintech

نقرة واحدة، خصم مزدوج: كيف أنقذني مفتاح ‘عدم التكرار’ (Idempotency Key) من غضب العملاء وكوابيس التسويات المالية؟

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

7 مارس، 2026 قراءة المزيد
أتمتة العمليات

من كابوس يوم الخميس إلى الإبداع المستمر: كيف حررتني أنابيب CI/CD من جحيم “يوم النشر”؟

أشارككم قصتي مع "يوم الخميس المشؤوم"، اليوم الذي كان مخصصًا لنشر التحديثات يدويًا، وكيف أنقذتني أتمتة العمليات باستخدام أنابيب CI/CD. اكتشفوا معي كيف تحولت من...

6 مارس، 2026 قراءة المزيد
الشبكات والـ APIs

طلبتُ حقلًا واحدًا، فأرسل لي الـ API قاعدة البيانات بأكملها: كيف أنقذني GraphQL من إهدار الباندويث والبيانات غير اللازمة؟

أشارككم قصة حقيقية من مسيرتي كمطور، حين كاد تطبيق جوال أن يفشل بسبب بطء استجابة الـ API. أستعرض كيف أنقذتني تقنية GraphQL من مشاكل إحضار...

5 مارس، 2026 قراءة المزيد
اختبارات الاداء والجودة

اختبارات التكامل قتلت إنتاجيتي: كيف أنقذني ‘اختبار العقود’ من جحيم انتظار الفرق الأخرى

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

2 مارس، 2026 قراءة المزيد
البودكاست