كنا نطلق الميزات على أمل ألا ينهار النظام: كيف أنقذنا اختبار الحِمل (Load Testing) باستخدام k6 من جحيم التخمين؟

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

جاءت ساعة الصفر، ضغطنا زر النشر، وراقبنا الشاشات. في الدقائق الأولى، كان الوضع “لوز” كما نقول في فلسطين. كل شيء يعمل بسلاسة. بدأت رسائل التهنئة تتبادل بين أعضاء الفريق. ولكن، الفرحة لم تدم طويلاً. فجأة، بدأت التنبيهات تنهال علينا كالمطر: “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، واكتب اختباراً بسيطاً لأحد مشاريعك. صدقني، راحة البال التي ستشعر بها قبل كل إطلاق ميزة جديدة لا تقدر بثمن. 🙏

أبو عمر

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

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

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

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

آخر المدونات

ادارة الفرق والتنمية البشرية

كان الجميع يهز رأسه موافقاً: كيف أنقذتنا ‘ثقافة الأمان النفسي’ من جحيم الإجماع الزائف؟

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

11 مايو، 2026 قراءة المزيد
أتمتة العمليات

كانت ‘ليالي الإطلاق’ كابوساً: كيف أنقذنا ‘خط أنابيب CI/CD’ من جحيم النشر اليدوي؟

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

10 مايو، 2026 قراءة المزيد
نصائح برمجية

كان إطلاق الميزات الجديدة كابوساً: كيف أنقذتنا ‘أعلام الميزات’ (Feature Flags) من جحيم عمليات النشر عالية المخاطر؟

تذكرون تلك الليالي الطوال التي نقضيها في إصلاح الأخطاء بعد كل عملية نشر؟ في هذه المقالة، أشارككم قصة كيف حولت 'أعلام الميزات' (Feature Flags) عمليات...

10 مايو، 2026 قراءة المزيد
​معمارية البرمجيات

لماذا اتخذنا هذا القرار؟: كيف أنقذتنا ‘سجلات القرارات المعمارية’ (ADRs) من جحيم النسيان

أشارككم قصة حقيقية من قلب المعركة البرمجية، وكيف أن أداة بسيطة تُدعى "سجلات القرارات المعمارية" (ADRs) أصبحت طوق النجاة لفريقي. اكتشفوا كيف يمكن لتوثيق "لماذا"...

10 مايو، 2026 قراءة المزيد
خوارزميات

كان البحث في موقعنا كالبحث عن إبرة في كومة قش: كيف أنقذتنا ‘خوارزمية البحث الثنائي’ من جحيم تجربة المستخدم البطيئة؟

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

10 مايو، 2026 قراءة المزيد
تسويق رقمي

كنا نستهدف الجمهور الخطأ: كيف أنقذتنا ‘التجزئة التنبؤية بالذكاء الاصطناعي’ من جحيم هدر ميزانية الإعلانات؟

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

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

كان كل فريق يغني على ليلاه: كيف أنقذ “نظام التصميم” مشروعنا من الفوضى البصرية؟

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

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