يا جماعة، السلام عليكم ورحمة الله. اسمي أبو عمر، وخلوني أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة علّمتني درس ما بنساه طول حياتي في عالم البرمجة.
كنا شغالين شهور طويلة على تطبيق جديد، تطبيق واعد صرفنا عليه كل وقتنا وجهدنا. الليالي الطويلة، القهوة اللي ما كانت تفارق مكاتبنا، النقاشات الحادة حول كل فاصلة ونقطة في الكود… كل هذا كان على وشك أن يتوج في يوم الإطلاق الكبير. الأجواء كانت حماسية، طلبنا بيتزا ووزعنا المهام الأخيرة. كل شيء كان جاهزًا، والاختبارات الوظيفية (Functional Tests) كلها ناجحة 100%. ضغطنا على زر “Go Live” وقلوبنا بترقص من الفرحة والترقب.
أول 10 دقائق كانت رائعة، الأرقام بدأت ترتفع، المستخدمون يسجلون دخولهم… وفجأة، بدأت الكارثة. الإشعارات بدأت تنهال علينا: “Error 503: Service Unavailable”، “Request Timed Out”. لوحة المراقبة (Dashboard) تحولت للون الأحمر بالكامل. التطبيق صار بطيئًا جدًا، لدرجة أنه أصبح غير قابل للاستخدام. حاولنا نعمل إعادة تشغيل للسيرفرات، ما زبط. حاولنا نزيد عدد الـ instances، تحسن طفيف ثم عاد الانهيار. شعور العجز والخيبة في تلك الليلة كان قاتلًا. كل تعب الشهور الماضية كان على وشك أن يتبخر في ساعات قليلة. والله يا جماعة، حسيت قلبي رح يوقف.
بعد ليلة طويلة وصعبة، أوقفنا الإطلاق مؤقتًا. الدرس القاسي الذي تعلمناه هو: تطبيقنا يعمل بشكل ممتاز… لمستخدم واحد أو عشرة. لكنه ينهار تمامًا تحت ضغط مئات أو آلاف المستخدمين المتزامنين. لم نكن مستعدين للنجاح الذي كنا نأمله. وهنا كانت بداية رحلتنا الحقيقية مع ما يسمى بـ “اختبار الإجهاد” أو الـ Stress Testing.
ما هو اختبار الإجهاد (Stress Testing)؟ وليش هو مش رفاهية؟
خليني أبسطها. تخيل إنك بتبني جسر. بعد ما تخلّص بناء، هل بتفتتحه مباشرة للسيارات؟ طبعًا لأ. المنطق بيقول إنك لازم تختبره. بتجيب شاحنات ثقيلة، أثقل من الحمل الطبيعي المتوقع، وبتضل تحمّل عليه وتزيد الوزن لحد ما تشوف وين أقصى قدرة تحمّل له، أو وين نقاط الضعف اللي ممكن تظهر تحت الضغط الشديد. هذا بالزبط هو اختبار الإجهاد.
في عالم البرمجيات، اختبار الإجهاد هو نوع من اختبارات الأداء (Performance Testing) هدفه مش يشوف إذا التطبيق شغال أو لأ، بل هدفه هو كسر التطبيق عن قصد. نعم، مثل ما قرأت. إحنا بنحاول نوصل التطبيق لنقطة الانهيار عشان نعرف:
- ما هي أقصى سعة له؟ (كم مستخدم متزامن بيقدر يتحمل قبل ما ينهار؟)
- كيف ينهار؟ (هل ينهار بشكل “كريم” ويعطي رسالة خطأ واضحة، أم يتجمد تمامًا؟)
- هل يستعيد عافيته؟ (بعد إزالة الضغط، هل يعود للعمل بشكل طبيعي تلقائيًا؟)
- أين هي عنق الزجاجة (Bottleneck)؟ (هل المشكلة في قاعدة البيانات؟ في الشبكة؟ في الكود نفسه؟)
الفرق بين اختبار الإجهاد (Stress)، اختبار الحمل (Load)، واختبار النقع (Soak)
كثير ناس بتخلط بينهم، والموضوع بسيط:
- اختبار الحمل (Load Testing): نختبر التطبيق تحت الحمل المتوقع والطبيعي. مثلًا، نتوقع 1000 مستخدم في ساعة الذروة، فبنشغل اختبار يحاكي 1000 مستخدم. الهدف هو التأكد من أن النظام يعمل جيدًا تحت الظروف الطبيعية.
- اختبار الإجهاد (Stress Testing): نختبر التطبيق تحت حمل أعلى بكثير من المتوقع (مثلاً 200% أو 300% من الحمل الطبيعي) بهدف إيجاد نقطة الانهيار.
- اختبار النقع (Soak/Endurance Testing): نختبر التطبيق تحت حمل طبيعي ولكن لفترة طويلة جدًا (ساعات أو أيام) بهدف اكتشاف مشاكل مثل تسريب الذاكرة (Memory Leaks).
كيف أنقذنا ‘اختبار الإجهاد’؟ خطواتنا العملية
بعد ليلة الكابوس، اجتمعنا وقررنا ما نرجع نطلق التطبيق إلا وإحنا واثقين 100% من قدرته على التحمل. هاي كانت خطتنا العملية اللي اتبعناها، واللي بنصح كل فريق يتبعها.
الخطوة الأولى: تحديد الأهداف والسيناريوهات الحرجة
ما بتقدر تختبر كل شيء. لازم تركز. جلسنا وحددنا أهم العمليات اللي بيقوم فيها المستخدم (User Journeys):
- تسجيل مستخدم جديد.
- تسجيل الدخول.
- تصفح المنتجات (هاي عملية قراءة ثقيلة على قاعدة البيانات).
- إضافة منتج للسلة.
- إتمام عملية الشراء (أهم عملية على الإطلاق!).
حددنا هدفًا: النظام يجب أن يتحمل 5000 مستخدم متزامن مع زمن استجابة (Response Time) أقل من 500ms للعمليات الرئيسية، ونريد أن نعرف ماذا سيحدث عند 10,000 و 15,000 مستخدم.
الخطوة الثانية: اختيار الأداة المناسبة
هناك الكثير من الأدوات الرائعة، بعضها مفتوح المصدر وبعضها مدفوع. من أشهرها:
- Apache JMeter: أداة قوية جدًا ومجانية، لكنها تحتاج لخبرة وواجهتها قديمة شوي.
- Gatling: ممتازة وتعتمد على لغة Scala، معروفة بأدائها العالي.
- k6 (من Grafana Labs): هي اللي اخترناها. أداة حديثة، مفتوحة المصدر، سهلة الاستخدام وتستخدم لغة JavaScript (أو TypeScript)، وهذا كان مناسب جدًا لفريقنا اللي معظمهم مطوري ويب.
نصيحة أبو عمر: إذا فريقك متعود على JavaScript، فـ k6 هي خيار رائع للبدء. سهولة كتابة السكربتات وتكاملها مع أدوات المراقبة الحديثة بيعطيها أفضلية كبيرة.
الخطوة الثالثة: كتابة سكربت الاختبار
هون بتبدأ المتعة الحقيقية. باستخدام k6، كتبنا سكربت يحاكي سلوك المستخدمين. هذا مثال مبسط جدًا لسكربت يحاكي زيارة الصفحة الرئيسية وتسجيل الدخول:
import http from 'k6/http';
import { check, sleep } from 'k6';
// هذا هو الجزء اللي بنحدد فيه شكل الاختبار
// هنا بنقول له: ابدأ بـ 0 مستخدم، وصّلهم لـ 200 مستخدم خلال 30 ثانية
// بعدين خليك على 200 مستخدم لمدة دقيقة
// بعدين نزّل العدد لـ 0 خلال 30 ثانية
export const options = {
stages: [
{ duration: '30s', target: 200 }, // ramp-up
{ duration: '1m', target: 200 }, // stable load
{ duration: '30s', target: 0 }, // ramp-down
],
// هون بنحدد شروط النجاح. إذا 95% من الطلبات أبطأ من 800ms، اعتبر الاختبار فاشل
thresholds: {
'http_req_duration': ['p(95)<800'],
},
};
// هذا هو الكود اللي كل مستخدم افتراضي (VU) راح ينفذه
export default function () {
// 1. المستخدم يزور الصفحة الرئيسية
const res = http.get('https://yourapp.com');
check(res, { 'status was 200': (r) => r.status == 200 });
sleep(1); // المستخدم ينتظر ثانية
// 2. المستخدم يقوم بتسجيل الدخول
const payload = JSON.stringify({
email: 'testuser@example.com',
password: 'supersecretpassword',
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
const loginRes = http.post('https://yourapp.com/api/login', payload, params);
check(loginRes, {
'login successful': (r) => r.status === 200,
'has auth token': (r) => r.json('token') !== '',
});
sleep(1);
}
طبعًا السكربت الحقيقي كان أعقد بكثير، فيه منطق لسحب بيانات مستخدمين مختلفين من ملف CSV حتى لا نختبر بنفس المستخدم كل مرة، وفيه كل السيناريوهات الخمسة اللي حددناها.
الخطوة الرابعة: التنفيذ وتحليل النتائج
شغلنا الاختبار لأول مرة… وكانت النتائج كارثية كما توقعنا. النظام بدأ ينهار عند حوالي 800 مستخدم فقط! نسبة الأخطاء ارتفعت بشكل جنوني، وزمن الاستجابة وصل لـ 15 ثانية! لكن هذه المرة، لم تكن كارثة، بل كانت بيانات. بيانات قيمة جدًا.
باستخدام أدوات المراقبة (مثل Prometheus و Grafana)، استطعنا أن نرى بالضبط أين حدثت المشكلة:
- عنق الزجاجة الأول: استعلام (Query) معين في قاعدة البيانات يأخذ وقتًا طويلًا جدًا تحت الضغط. لم يكن يستخدم Indexing بشكل صحيح.
- عنق الزجاجة الثاني: خدمة خارجية (Third-party API) كنا نعتمد عليها للتأكد من صحة العناوين كانت بطيئة جدًا، وكل طلب لها كان يحجز Thread على السيرفر.
- عنق الزجاجة الثالث: اكتشفنا تسريب ذاكرة بسيط في إحدى الخدمات الصغيرة (Microservice) كان يتراكم مع الوقت.
الخطوة الخامسة: الإصلاح والتكرار
هنا يبدأ العمل الحقيقي. عالجنا المشاكل واحدة تلو الأخرى:
- أضفنا الـ Index المناسب لقاعدة البيانات. النتيجة: زمن الاستجابة لهذا الـ Query نزل من 2000ms إلى 20ms تحت الضغط.
- قمنا بعمل Caching لنتائج الـ API الخارجية. بدلًا من مناداتها في كل مرة، أصبحنا نطلبها مرة واحدة ونخزن النتيجة لمدة ساعة.
- أصلحنا تسريب الذاكرة في الخدمة الصغيرة.
وبعد كل إصلاح، كنا نعيد تشغيل اختبار الإجهاد مرة أخرى. نعم، هي عملية تكرارية (Iterative Process). نختبر، نحلل، نصلح، ثم نختبر مرة أخرى. كررنا هذه الدورة حوالي 5 مرات. في كل مرة، كنا نرى الرقم الذي ينهار عنده النظام يرتفع: من 800 إلى 2000، ثم 4500، ثم 7000، حتى تجاوزنا هدفنا الأولي بكثير.
نصائح من قلب الميدان
- ابدأ مبكرًا: لا تنتظر لنهاية المشروع. قم بعمل اختبارات أداء بسيطة مع كل ميزة جديدة تضيفها.
- اجعله آليًا (Automate): ادمج اختبارات الأداء ضمن الـ CI/CD pipeline. اجعلها تُنفّذ تلقائيًا كل ليلة مثلًا، حتى تكتشف أي تدهور في الأداء بشكل فوري.
- الاختبار في بيئة شبيهة بالإنتاج: نتائج الاختبار على لابتوبك لا تعني شيئًا. يجب أن يتم الاختبار على بيئة (Staging Environment) مواصفاتها مطابقة أو قريبة جدًا من بيئة الإنتاج (Production).
- المراقبة أهم من الاختبار نفسه: تشغيل الاختبار بدون مراقبة السيرفرات وقواعد البيانات والموارد هو مجرد إضاعة للوقت. أنت بحاجة لتعرف *لماذا* النظام بطيء، وليس فقط *أنه* بطيء.
- إنه عمل جماعي: اختبار الأداء ليس مسؤولية قسم الجودة (QA) فقط. هو مسؤولية المطورين، مهندسي DevOps، ومدراء المنتجات. الكل يجب أن يفهم النتائج ويشارك في الحل.
الخلاصة: لا تنتظر الكابوس 🚀
بعد أسبوعين من العمل المكثف على اختبارات الإجهاد والإصلاحات، أعدنا إطلاق التطبيق. هذه المرة، كنا نراقب لوحة التحكم بثقة وليس بخوف. رأينا عدد المستخدمين يرتفع إلى الآلاف، والنظام صامد وثابت كالصخر. زمن الاستجابة كان ممتازًا، ونسبة الأخطاء كانت تقريبًا صفر. الشعور بالنجاح في تلك اللحظة كان لا يوصف، لأنه لم يكن نجاحًا بالصدفة، بل كان نجاحًا مبنيًا على علم وبيانات وعمل شاق.
اختبار الإجهاد ليس ترفًا أو شيئًا إضافيًا نفعله لو “توفّر الوقت”. إنه جزء أساسي من هندسة البرمجيات الموثوقة، وهو بمثابة بوليصة تأمين ضد كابوس الانهيار يوم الإطلاق. لا تنتظروا الكارثة لكي تتعلموا الدرس الذي تعلمناه بالطريقة الصعبة. ابدأوا اليوم، اختبروا أنظمتكم، واعرفوا حدودها قبل أن يكتشفها المستخدمون بأنفسهم.