أذكرها وكأنها البارحة، ليلة إطلاق تحديث كبير لأحد تطبيقات التجارة الإلكترونية التي كنا نعمل عليها. كان ذلك قبل “موسم التخفيضات الكبير”، والكل كان على أعصابه. فريق التسويق أطلق حملاته، والعميل ينتظر مبيعات قياسية، ونحن فريق المطورين، كنا نضع اللمسات الأخيرة على الكود. أتذكر أن مديرنا التقني كان يمشي ذهاباً وإياباً في المكتب، يرتشف قهوته وكأنه ينتظر نتيجة امتحان مصيري.
جاءت ساعة الصفر، ضغطنا زر النشر، وراقبنا الشاشات. في الدقائق الأولى، كان الوضع “لوز” كما نقول في فلسطين. كل شيء يعمل بسلاسة. بدأت رسائل التهنئة تتبادل بين أعضاء الفريق. ولكن، الفرحة لم تدم طويلاً. فجأة، بدأت التنبيهات تنهال علينا كالمطر: “High CPU Usage”, “503 Service Unavailable”, “Database Connection Timeout”. انهار النظام. تحولت ليلة الاحتفال إلى كابوس من إعادة تشغيل الخوادم يدوياً، والبحث اليائس عن سبب العطل في سجلات الأخطاء (logs)، والرد على سيل من رسائل العملاء الغاضبين. في تلك الليلة، لم نكن مبرمجين، بل كنا رجال إطفاء نحاول إخماد حريق أشعلناه بأنفسنا.
هذه الحادثة كانت جرس إنذار قوياً لنا. أدركنا أننا كنا نطلق الميزات ونحن نعتمد على الدعاء والأمل، بدلاً من الاعتماد على البيانات والأرقام. من هنا بدأت رحلتنا مع اختبارات الأداء، وتحديداً مع أداة k6 التي أنقذتنا من جحيم التخمين.
لماذا كنا نعيش في جحيم التخمين؟
قبل أن أغوص في تفاصيل الحل، من المهم أن نفهم أصل المشكلة. لم نكن فريقاً سيئاً، بل على العكس، كنا نتبع أفضل الممارسات في كتابة الكود وإجراء الاختبارات. إذاً، أين كانت المشكلة؟
الاعتماد على الحدس لا الأرقام
كنا نعتقد أن الكود الذي نكتبه فعال، وأن البنية التحتية التي نستخدمها قوية. لكن هذا كله كان مجرد “حدس”. لم يكن لدينا أي رقم حقيقي يخبرنا: “كم عدد المستخدمين المتزامنين الذين يمكن للنظام تحملهم؟” أو “ما هو متوسط زمن الاستجابة تحت ضغط 1000 طلب في الدقيقة؟”. كنا نطير عمياناً.
الاختبارات الوظيفية لا تكفي
كان لدينا تغطية اختبارات ممتازة (unit, integration, E2E tests). كل الاختبارات كانت خضراء قبل النشر. لكن هذه الاختبارات تجيب على سؤال “هل الميزة تعمل؟” (Does it work?)، لكنها لا تجيب أبداً على سؤال “هل ستستمر الميزة بالعمل تحت الضغط؟” (Will it work at scale?).
الخوف من المجهول
بصراحة، كنا نهاب عالم اختبارات الأداء. كان يبدو لنا معقداً ومكلفاً، ويتطلب أدوات باهظة الثمن وخبرات متخصصة. لم نكن نعرف من أين نبدأ، فكان أسهل خيار هو تجاهل المشكلة على أمل ألا تحدث.
نقطة التحول: اكتشاف k6
بعد ليلة الكارثة تلك، عقدنا العزم على أن يتغير كل شيء. بدأنا رحلة بحث عن أدوات اختبار الأداء (Performance Testing Tools). جربنا بعض الأدوات الكلاسيكية مثل JMeter، لكنها كانت تبدو معقدة وذات واجهة استخدام قديمة. إلى أن عثرنا على k6.
لماذا k6 بالذات؟
k6 كانت بمثابة نسمة هواء منعشة. لقد صُممت من قبل المطورين، ولأجل المطورين. وهذا ما جعلها خيارنا الأول للأسباب التالية:
- صديقة للمطورين (Developer-Friendly): تُكتب نصوص الاختبار (scripts) بلغة JavaScript (ES6)، وهي لغة يعرفها ويحبها كل مطور ويب. لا حاجة لتعلم لغة جديدة أو التعامل مع واجهات رسومية معقدة.
- أداء عالٍ: الأداة نفسها مكتوبة بلغة Go، مما يجعلها قادرة على توليد حِمل كبير من جهاز واحد دون استهلاك موارد ضخمة.
- مفتوحة المصدر ومجتمعية: لديها مجتمع نشط، وتوثيق ممتاز، والنسخة مفتوحة المصدر قوية جداً وتلبي معظم الاحتياجات.
- الفحوصات والعتبات (Checks and Thresholds): هذه كانت الميزة القاتلة بالنسبة لنا. تتيح لك k6 تعريف معايير النجاح والفشل مباشرة داخل الكود. على سبيل المثال: “يجب أن يكون معدل الأخطاء أقل من 1%” و “يجب أن يكون 95% من الطلبات أسرع من 500ms”.
- قابلة للتوسعة والدمج: يمكن دمجها بسهولة تامة مع أنظمة التكامل المستمر والنشر المستمر (CI/CD) مثل Jenkins, GitLab CI, GitHub Actions.
لنبدأ العمل: أول اختبار حِمل لنا مع k6
الكلام النظري جميل، لكن دعونا نرى كيف يمكننا استخدام k6 بشكل عملي. سأريكم مدى سهولة البدء.
تثبيت k6 (أسهل مما تتخيل)
عملية التثبيت مباشرة جداً. يمكنك زيارة الموقع الرسمي واتباع التعليمات الخاصة بنظام التشغيل لديك. على سبيل المثال، على نظام macOS يمكنك تثبيته بأمر بسيط:
brew install k6
كتابة أول سكربت: اختبار بسيط لواجهة برمجية (API)
لنفترض أننا نريد اختبار واجهة برمجية بسيطة. كل ما نحتاجه هو إنشاء ملف باسم script.js وكتابة الكود التالي:
import http from 'k6/http';
import { sleep } from 'k6';
// خيارات الاختبار
export const options = {
// تعريف عدد المستخدمين الافتراضيين (Virtual Users) ومدة الاختبار
vus: 10, // 10 مستخدمين يرسلون الطلبات بشكل متزامن
duration: '30s', // يستمر الاختبار لمدة 30 ثانية
};
// الدالة الافتراضية التي سيتم تنفيذها بشكل متكرر من قبل كل مستخدم افتراضي
export default function () {
// إرسال طلب GET إلى واجهة برمجية تجريبية
const res = http.get('https://test-api.k6.io/public/crocodiles/1/');
// التوقف المؤقت لمدة ثانية واحدة لمحاكاة تفكير المستخدم قبل الطلب التالي
sleep(1);
}
هذا الكود بسيط وواضح. نحن نُعرّف 10 مستخدمين افتراضيين (VUs) سيقومون بإرسال طلبات بشكل متزامن لمدة 30 ثانية. كل مستخدم سيقوم بإرسال طلب GET ثم ينتظر لمدة ثانية.
تشغيل الاختبار وتحليل النتائج
لتشغيل الاختبار، نفتح الطرفية (Terminal) في نفس مجلد الملف ونكتب الأمر:
k6 run script.js
بعد انتهاء الاختبار، ستظهر لك k6 ملخصاً جميلاً للنتائج. لا تخافوا من كثرة الأرقام، سأشرح أهمها:
...
✓ status was 200
checks.........................: 100.00% ✓ 125 ✗ 0
data_received..................: 37 kB 1.2 kB/s
data_sent......................: 11 kB 377 B/s
http_req_blocked...............: avg=2.83ms min=5.2µs med=8.1µs max=55.51ms p(90)=12.5µs p(95)=14.12µs
http_req_connecting............: avg=912.4µs min=0s med=0s max=17.75ms p(90)=0s p(95)=0s
http_req_duration..............: avg=137.91ms min=130.3ms med=135.5ms max=171.1ms p(90)=143.2ms p(95)=146.9ms
{ expected_response:true }...: avg=137.91ms min=130.3ms med=135.5ms max=171.1ms p(90)=143.2ms p(95)=146.9ms
http_req_failed................: 0.00% ✓ 0 ✗ 125
http_req_receiving.............: avg=153.2µs min=45.2µs med=137.5µs max=434.6µs p(90)=210µs p(95)=250.3µs
http_req_sending...............: avg=42.4µs min=13.1µs med=38.6µs max=134.4µs p(90)=61.6µs p(95)=74.1µs
http_req_tls_handshaking.......: avg=1.9ms min=0s med=0s max=37.7ms p(90)=0s p(95)=0s
http_req_waiting...............: avg=137.7ms min=130.1ms med=135.3ms max=170.9ms p(90)=143ms p(95)=146.7ms
http_reqs......................: 125 4.160756/s
iteration_duration.............: avg=1.13s min=1.13s med=1.13s max=1.17s p(90)=1.14s p(95)=1.14s
iterations.....................: 125 4.160756/s
vus............................: 10 min=10 max=10
vus_max........................: 10 min=10 max=10
http_req_duration: هذا هو المقياس الأهم. إنه يخبرك كم من الوقت استغرق الطلب من إرساله حتى استلام الرد كاملاً. ستلاحظ وجودavg(المتوسط)،min(الأقل)،max(الأقصى)، والأهم من ذلكp(95).p(95)أو (95th percentile): هذا الرقم يعني أن 95% من طلباتك كانت أسرع من هذه القيمة. إنه مقياس أكثر واقعية من المتوسط، لأنه يتجاهل القيم المتطرفة (الـ 5% الأبطأ) ويعطيك فكرة حقيقية عن تجربة معظم المستخدمين.http_req_failed: نسبة الطلبات التي فشلت. هدفك دائماً أن يكون هذا الرقم 0% أو قريباً جداً منه.http_reqs: العدد الإجمالي للطلبات التي تم إرسالها خلال الاختبار.
من البساطة إلى الاحتراف: سيناريوهات متقدمة
الاختبار السابق كان مجرد بداية. قوة k6 الحقيقية تظهر في السيناريوهات الأكثر تعقيداً التي تحاكي سلوك المستخدم الحقيقي.
الـ Thresholds: خط الدفاع الأول ضد تدهور الأداء
هنا يا جماعة الخير، تبدأ المتعة الحقيقية. الأرقام وحدها لا تكفي. يجب أن نُعلّم الاختبار ما هو “النجاح” وما هو “الفشل”. هذا ما تفعله العتبات (Thresholds). لنقم بتعديل خياراتنا في ملف script.js:
export const options = {
vus: 10,
duration: '30s',
thresholds: {
// 95% من الطلبات يجب أن تكتمل في أقل من 200 ميللي ثانية
'http_req_duration': ['p(95)<200'],
// معدل الأخطاء يجب أن يكون أقل من 1%
'http_req_failed': ['rate<0.01'],
// يجب أن يكون عدد الطلبات المنفذة أكثر من 20
'http_reqs': ['count>20'],
},
};
الآن، عندما تشغل الاختبار، لن يكتفي k6 بعرض الأرقام، بل سيخبرك بصراحة إذا ما تم تجاوز هذه العتبات أم لا. سيظهر علامة (✓) خضراء بجانب كل شرط تم تحقيقه، وعلامة (✗) حمراء بجانب كل شرط فشل. هذا يجعل عملية التحقق آلية ومثالية لدمجها في الـ CI/CD. إذا فشل اختبار الأداء، يفشل الـ build بأكمله، ويتم منع نشر الكود الذي سبب التدهور.
محاكاة سلوك المستخدم الحقيقي (Scenarios)
المستخدمون لا يزورون صفحة واحدة فقط. قد يتصفحون المنتجات، ثم يضيفون منتجاً إلى السلة، ثم يقومون بتسجيل الدخول. يمكننا محاكاة هذا السلوك المعقد باستخدام سيناريوهات k6.
import { scenario } from 'k6/execution';
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
scenarios: {
// سيناريو للمستخدمين الذين يتصفحون فقط
browse_scenario: {
executor: 'constant-vus',
vus: 5,
duration: '1m',
exec: 'browse', // يربط السيناريو بالدالة 'browse'
},
// سيناريو للمستخدمين الذين يقومون بعملية الشراء
checkout_scenario: {
executor: 'per-vu-iterations',
vus: 2,
iterations: 5,
exec: 'checkout', // يربط السيناريو بالدالة 'checkout'
startTime: '30s', // يبدأ هذا السيناريو بعد 30 ثانية
},
},
};
export function browse() {
http.get('https://example.com/products');
sleep(2);
}
export function checkout() {
http.get('https://example.com/cart');
sleep(1);
http.post('https://example.com/checkout', {});
}
في هذا المثال المتقدم، قمنا بتعريف سيناريوهين مختلفين يعملان بالتوازي، كل منهما يحاكي سلوكاً مختلفاً، وبأنماط حِمل مختلفة. هذا يعطينا صورة أقرب بكثير للواقع.
نصائح من قلب الميدان (خبرة أبو عمر)
بعد سنوات من استخدام هذه الأداة، اسمحوا لي أن أقدم لكم بعض النصائح العملية التي تعلمتها بالطريقة الصعبة:
- ابدأ صغيراً ثم توسّع: لا تحاول محاكاة نظامك بأكمله في اليوم الأول. ابدأ بأهم واجهة برمجية (API) لديك، تلك التي تتلقى معظم الطلبات أو التي تسبب معظم المشاكل. اكتب لها اختباراً بسيطاً، ثم أضف التعقيد تدريجياً.
- لا تختبر على بيئة الإنتاج (Production)! (إلا إذا كنت تعرف تماماً ماذا تفعل): اختبار الحِمل قد يؤدي إلى انهيار الخوادم. دائماً قم بالاختبار على بيئة اختبار (Staging) تكون مطابقة لبيئة الإنتاج قدر الإمكان من حيث المواصفات والبيانات.
- اختبر باستمرار: لا تجعل اختبار الأداء حدثاً سنوياً. قم بدمجه في الـ CI/CD الخاص بك. قم بتشغيل اختبار حِمل صغير مع كل pull request، واختبار أكبر مع كل عملية نشر على بيئة الاختبار. هذا يساعدك على اكتشاف تدهور الأداء في وقت مبكر جداً.
- الأرقام وحدها لا تكفي، اربطها بالمراقبة: k6 يخبرك أن “الواجهة X بطيئة”. لكنه لا يخبرك “لماذا”. يجب أن تراقب خوادمك (CPU, Memory, Disk I/O) وقاعدة بياناتك (Slow Queries) أثناء تشغيل الاختبار. أدوات مثل Prometheus, Grafana, Datadog هي صديقك هنا.
- حدد أهدافك قبل الاختبار: قبل كتابة سطر كود واحد، اسأل نفسك: “ما هو الهدف من هذا الاختبار؟”. هل تريد معرفة نقطة الانهيار (Breaking Point)؟ أم تريد التأكد من أن النظام يستطيع خدمة 1000 مستخدم بزمن استجابة أقل من 300ms؟ تحديد الهدف يحدد شكل الاختبار.
الخلاصة: من الدعاء إلى البيانات 📊
رحلتنا مع اختبارات الأداء، وتحديداً مع k6، غيرت طريقة عملنا بشكل جذري. انتقلنا من فريق يعيش في قلق دائم قبل كل عملية نشر، إلى فريق واثق من قدرة نظامه على التحمل، مسلح بالبيانات والأرقام. لم نعد نطلق الميزات على أمل ألا ينهار النظام، بل أصبحنا نطلقها ونحن نعرف بالضبط قدرة النظام على التحمل.
اختبار الأداء ليس ترفاً، بل هو جزء أساسي من دورة حياة تطوير أي تطبيق جاد. وأداة مثل k6 جعلت هذا الأمر في متناول جميع المطورين، بغض النظر عن حجم الفريق أو الميزانية.
نصيحتي الأخيرة لك: لا تنتظر الكارثة لتبدأ. ابدأ اليوم، ولو بخطوة صغيرة. قم بتثبيت k6، واكتب اختباراً بسيطاً لأحد مشاريعك. صدقني، راحة البال التي ستشعر بها قبل كل إطلاق ميزة جديدة لا تقدر بثمن. 🙏