k6 Load Testing: Stress-Test Your APIs Before Launch

Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.

E
ECOSIRE Research and Development Team
|19 مارچ، 202610 منٹ پڑھیں2.3k الفاظ|

ہماری Performance & Scalability سیریز کا حصہ

مکمل گائیڈ پڑھیں

k6 لوڈ ٹیسٹنگ: اپنے APIs کو لانچ کرنے سے پہلے اسٹریس ٹیسٹ کریں۔

لوڈ ٹیسٹنگ کے بغیر مصنوعات کی ترسیل ایک جوا ہے۔ آپ کو اپنے بریکنگ پوائنٹ کا علم نہیں ہوتا جب تک کہ صارفین اسے آپ کے لیے تلاش نہ کریں — عام طور پر پروڈکٹ لانچ، وائرل لمحے، یا سیلز میں اضافے کے دوران۔ k6 ایک جدید لوڈ ٹیسٹنگ ٹول ہے جو آپ کو JavaScript میں ٹیسٹ لکھنے، انہیں CI سے چلانے اور صارفین سے پہلے اپنی کارکردگی کی حد دریافت کرنے دیتا ہے۔ یہ ڈویلپر کے لیے دوستانہ، وسائل کے لحاظ سے موثر ہے (k6 goroutines کا استعمال کرتا ہے، نہ کہ تھریڈز)، اور ریئل ٹائم میٹرکس کے لیے Grafana اور Prometheus کے ساتھ صاف طور پر ضم ہوتا ہے۔

یہ گائیڈ پہلی اسکرپٹ سے پیچیدہ ملٹی سیناریو لوڈ ٹیسٹ، کسٹم میٹرکس، تھریشولڈز، CI انضمام، اور Node.js/NestJS APIs کے لیے پروڈکشن کے لیے محفوظ ٹیسٹنگ پیٹرن کے ذریعے k6 کا احاطہ کرتی ہے۔

اہم ٹیک ویز

  • k6 اسکرپٹ جاوا اسکرپٹ ہیں لیکن گو رن ٹائم میں چلتی ہیں — کوئی Node.js APIs نہیں (کوئی require، کوئی fs، کوئی setTimeout)
  • ورچوئل یوزرز (VUs) ہم وقت ساز استعمال کنندہ ہیں۔ تکرار انفرادی اسکرپٹ پر عملدرآمد ہیں۔
  • دوڑنے سے پہلے ہمیشہ حد مقرر کریں - ناکام ہونے والی حدیں ٹیسٹ کو روکتی ہیں اور CI میں ناکام ہوجاتی ہیں
  • حقیقی ٹریفک پیٹرن کو ماڈل کرنے کے لیے منظرنامے (مستقل-vus، ramping-vus، constant-arrival-rate) کا استعمال کریں
  • آپریشن کے ساتھ ہم آہنگی کیے بغیر کبھی بھی ٹیسٹ پروڈکشن لوڈ نہ کریں - اسٹیجنگ یا پروڈکشن کلون کے خلاف ٹیسٹ
  • http_req_duration میٹرک آپ کا بنیادی SLA میٹرک ہے — p95 اور p99 اوسط سے زیادہ اہمیت رکھتے ہیں
  • اپنے k6 رنرز کو ریٹ محدود کریں — لوڈ ٹیسٹ ٹریفک سے آپ کی شرح کی حد کا بجٹ ختم نہیں ہونا چاہیے۔
  • جغرافیائی علاقوں میں تقسیم شدہ لوڈ ٹیسٹنگ کے لیے k6 Cloud یا Grafana k6 استعمال کریں

انسٹالیشن اور پہلا اسکرپٹ

# macOS
brew install k6

# Ubuntu/Debian
sudo gpg -k && sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6

# Windows (via Chocolatey)
choco install k6

آپ کی پہلی k6 اسکرپٹ:

// k6/homepage-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

// Custom metric: error rate
const errorRate = new Rate('error_rate');

export const options = {
  // Ramp up to 50 VUs over 1 minute, hold 2 minutes, ramp down
  stages: [
    { duration: '1m', target: 50 },
    { duration: '2m', target: 50 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th pct < 500ms
    http_req_failed:   ['rate<0.01'],                // Less than 1% errors
    error_rate:        ['rate<0.01'],
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';

export default function () {
  const res = http.get(`${BASE_URL}/`);

  const ok = check(res, {
    'status is 200':              (r) => r.status === 200,
    'response time < 500ms':     (r) => r.timings.duration < 500,
    'body contains ECOSIRE':     (r) => r.body.includes('ECOSIRE'),
  });

  errorRate.add(!ok);
  sleep(1); // 1 second think time between iterations
}

اسے چلائیں:

k6 run k6/homepage-load.js
k6 run --env BASE_URL=https://staging.ecosire.com k6/homepage-load.js
k6 run --vus 100 --duration 30s k6/homepage-load.js  # Quick smoke test

توثیق کے ساتھ API لوڈ ٹیسٹ

// k6/api-load.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';

const apiCalls = new Counter('api_calls');
const authDuration = new Trend('auth_duration');

export const options = {
  scenarios: {
    // Constant arrival rate: 100 requests/second regardless of VU count
    constant_load: {
      executor: 'constant-arrival-rate',
      rate: 100,
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },
  },
  thresholds: {
    http_req_duration:            ['p(95)<300', 'p(99)<500'],
    'http_req_duration{type:api}': ['p(95)<200'],
    http_req_failed:              ['rate<0.005'], // 0.5% error budget
  },
};

const BASE_URL = __ENV.BASE_URL || 'http://localhost:3001';

// setup() runs once before VUs start
export function setup() {
  // Authenticate and return tokens for VUs to use
  const loginRes = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });

  check(loginRes, { 'login successful': (r) => r.status === 200 });

  const { accessToken } = loginRes.json();
  return { accessToken };
}

export default function (data) {
  const { accessToken } = data;
  const headers = {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  };

  group('Contacts API', () => {
    // List contacts
    const listRes = http.get(`${BASE_URL}/contacts?page=1&limit=20`, {
      headers,
      tags: { type: 'api', endpoint: 'contacts-list' },
    });

    check(listRes, {
      'list contacts: 200': (r) => r.status === 200,
      'list contacts: has items': (r) => r.json('data') !== null,
    });

    apiCalls.add(1);
    sleep(0.5);

    // Create a contact
    const createRes = http.post(`${BASE_URL}/contacts`, JSON.stringify({
      name: `Load Test User ${Date.now()}`,
      email: `load-test-${Date.now()}@example.com`,
    }), {
      headers,
      tags: { type: 'api', endpoint: 'contacts-create' },
    });

    check(createRes, {
      'create contact: 201': (r) => r.status === 201,
    });

    apiCalls.add(1);
  });

  sleep(1);
}

// teardown() runs once after all VUs finish
export function teardown(data) {
  // Clean up test data if needed
  console.log(`Test complete. Data: ${JSON.stringify(data)}`);
}

ریمپنگ VU منظرنامے۔

متعدد مراحل اور منظرناموں کے ساتھ حقیقی ٹریفک کے نمونوں کو ماڈل کریں:

// k6/stress-test.js
export const options = {
  scenarios: {
    // Scenario 1: Normal traffic baseline
    normal_traffic: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 20 },
        { duration: '5m', target: 20 },
      ],
      gracefulRampDown: '30s',
    },

    // Scenario 2: Sudden spike (marketing campaign goes live)
    traffic_spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      startTime: '7m', // Starts after baseline is stable
      stages: [
        { duration: '30s', target: 200 }, // Rapid spike
        { duration: '2m',  target: 200 }, // Hold spike
        { duration: '30s', target: 20  }, // Fall back
      ],
    },

    // Scenario 3: Soak test for memory leaks
    soak_test: {
      executor: 'constant-vus',
      vus: 10,
      duration: '30m', // Run for 30 minutes
      startTime: '10m',
    },
  },
  thresholds: {
    'http_req_duration{scenario:normal_traffic}': ['p(95)<300'],
    'http_req_duration{scenario:traffic_spike}':  ['p(95)<800'],
    http_req_failed:                              ['rate<0.02'],
  },
};

بلاگ/سائٹ میپ کرال ٹیسٹ

// k6/sitemap-crawl.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

// Load URLs from sitemap (pre-fetched and saved as JSON)
const urls = new SharedArray('sitemap urls', function () {
  // In real usage, fetch sitemap XML and parse URLs beforehand
  // SharedArray is loaded once and shared across all VUs (memory efficient)
  return JSON.parse(open('./sitemap-urls.json'));
});

export const options = {
  vus: 20,
  duration: '5m',
  thresholds: {
    http_req_duration: ['p(95)<2000'], // Public pages can be slower
    http_req_failed:   ['rate<0.01'],
  },
};

export default function () {
  // Each VU picks a random URL from the sitemap
  const url = urls[Math.floor(Math.random() * urls.length)];

  const res = http.get(url, {
    headers: {
      // Identify as load tester in logs
      'User-Agent': 'k6-load-tester/1.0',
    },
    timeout: '10s',
  });

  check(res, {
    'status is 200':           (r) => r.status === 200,
    'no error page':           (r) => !r.body.includes('Error'),
    'has canonical tag':       (r) => r.body.includes('rel="canonical"'),
    'response time < 2000ms': (r) => r.timings.duration < 2000,
  });

  sleep(Math.random() * 2 + 1); // Random 1-3 second think time
}

حسب ضرورت میٹرکس اور حد

// k6/checkout-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Rate, Trend, Gauge } from 'k6/metrics';

// Custom metrics for business-specific tracking
const checkoutSuccesses = new Counter('checkout_successes');
const checkoutFailures  = new Counter('checkout_failures');
const checkoutDuration  = new Trend('checkout_duration');
const activeCheckouts   = new Gauge('active_checkouts');

export const options = {
  stages: [
    { duration: '1m', target: 10 },
    { duration: '3m', target: 30 },
    { duration: '1m', target: 0 },
  ],
  thresholds: {
    checkout_duration:   ['p(95)<3000'],   // Checkout < 3s at p95
    checkout_failures:   ['count<10'],     // Max 10 absolute failures
    http_req_duration:   ['p(99)<2000'],
  },
};

export default function (data) {
  activeCheckouts.add(1);
  const start = Date.now();

  // Step 1: Add to cart
  const cartRes = http.post('/api/cart/add', JSON.stringify({
    productId: 'prod_odoo_customization',
    quantity: 1,
  }), { headers: { 'Content-Type': 'application/json' } });

  if (!check(cartRes, { 'add to cart: 200': (r) => r.status === 200 })) {
    checkoutFailures.add(1);
    activeCheckouts.add(-1);
    return;
  }

  sleep(2);

  // Step 2: Create checkout session
  const checkoutRes = http.post('/api/billing/checkout', JSON.stringify({
    cartId: cartRes.json('cartId'),
  }), { headers: { 'Content-Type': 'application/json' } });

  const success = check(checkoutRes, {
    'checkout session created': (r) => r.status === 200,
    'has session URL':          (r) => r.json('url') !== null,
  });

  if (success) {
    checkoutSuccesses.add(1);
    checkoutDuration.add(Date.now() - start);
  } else {
    checkoutFailures.add(1);
  }

  activeCheckouts.add(-1);
  sleep(1);
}

گرافانا آؤٹ پٹ انٹیگریشن

K6 میٹرکس کو InfluxDB پر سٹریم کریں اور ٹیسٹ کے دوران گرافانا میں تصور کریں:

# Run with InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 k6/api-load.js

# Or use the k6 Grafana integration (k6 v0.45+)
k6 run --out experimental-prometheus-rw k6/api-load.js
# docker-compose.monitoring.yml
services:
  influxdb:
    image: influxdb:2.7
    ports: ["8086:8086"]
    environment:
      INFLUXDB_DB: k6
      INFLUXDB_ADMIN_USER: admin
      INFLUXDB_ADMIN_PASSWORD: password

  grafana:
    image: grafana/grafana:latest
    ports: ["3030:3000"]
    environment:
      GF_AUTH_ANONYMOUS_ENABLED: "true"
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources

CI انٹیگریشن

# .github/workflows/ci.yml (load test section)
load-tests:
  runs-on: ubuntu-latest
  if: github.ref == 'refs/heads/main' # Only on main branch
  needs: [deploy-staging]
  steps:
    - uses: actions/checkout@v4

    - name: Install k6
      run: |
        sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
          --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
        echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
          | sudo tee /etc/apt/sources.list.d/k6.list
        sudo apt-get update && sudo apt-get install k6

    - name: Run smoke test (30 VUs, 60s)
      run: |
        k6 run --vus 30 --duration 60s \
          --env BASE_URL=${{ secrets.STAGING_URL }} \
          --env TEST_EMAIL=${{ secrets.TEST_EMAIL }} \
          --env TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
          apps/api/k6/api-load.js

    - name: Upload k6 results
      uses: actions/upload-artifact@v4
      if: always()
      with:
        name: k6-results
        path: k6-results/

کارکردگی کی بنیادیں۔

رجعت پر بنیادی خطوط اور الرٹ قائم کریں:

اختتامی نقطہp50p95p99خرابی بجٹ
GET / (ہوم ​​پیج)120ms400ms800ms0.1%
GET /api/contacts50ms150ms300ms0.5%
POST /api/contacts80ms200ms400ms0.5%
POST /auth/login200ms500ms1000ms1%
POST /billing/checkout500ms1500ms3000ms1%
GET /blog/[slug]100ms300ms600ms0.1%

اکثر پوچھے گئے سوالات

VUs اور درخواستوں فی سیکنڈ میں کیا فرق ہے؟

VUs (ورچوئل یوزرز) ہم وقت سازی والے صارف ہیں۔ ہر VU آپ کا ڈیفالٹ فنکشن ایک لوپ میں چلاتا ہے۔ فی سیکنڈ کی درخواستوں کا انحصار اس بات پر ہے کہ ہر تکرار کتنی تیزی سے چلتی ہے۔ اگر ہر VU تکرار میں 2 سیکنڈ لگتے ہیں (بشمول نیند کا وقت) اور آپ کے پاس 100 VUs ہیں، تو آپ کو تقریباً 50 RPS ملتے ہیں۔ constant-arrival-rate executor کا استعمال کریں جب آپ کو کسی مخصوص RPS کو ٹارگٹ کرنے کی ضرورت ہو قطع نظر اس کے کہ تکرار کی مدت ہو۔

کیا مجھے پروڈکشن یا سٹیجنگ کے خلاف ٹیسٹ لوڈ کرنا چاہیے؟

ہمیشہ اسٹیجنگ کو ترجیح دیں۔ پروڈکشن لوڈ ٹیسٹ سے حقیقی صارفین پر اثر انداز ہونے، شرح کی حد کو ختم کرنے، ٹیسٹ ڈیٹا سے حقیقی آرڈرز بنانے اور حقیقی ویب ہک ایونٹس کو متحرک کرنے کا خطرہ ہوتا ہے۔ اگر آپ کو پروڈکشن کی جانچ کرنی ہے، تو اسے کم ٹریفک والی ونڈوز کے دوران کریں، ٹیسٹ پیمنٹ ٹوکن استعمال کریں، اور رول بیک پلان رکھیں۔ k6 کلاؤڈ جیو ڈسٹری بیوٹڈ لوڈ جنریشن کو سپورٹ کرتا ہے تاکہ آپ اصلیت کو چھوئے بغیر اپنے CDN ایج کی کارکردگی کو جانچ سکیں۔

میں طویل ٹیسٹوں کے دوران auth token کی میعاد ختم ہونے کو کیسے ہینڈل کروں؟

مختصر ٹیسٹوں کے لیے (15 منٹ سے کم)، setup() میں ایک ٹوکن حاصل کریں اور اسے data کے ذریعے تمام VUs کو منتقل کریں۔ لمبے ٹیسٹوں کے لیے (15 منٹ سے زیادہ)، ٹیسٹ اسکرپٹ میں ٹوکن ریفریش کو لاگو کریں: ڈیفالٹ فنکشن میں ٹوکن کی عمر کو چیک کریں اور جب اس کی میعاد ختم ہو جائے تو ریفریش کریں۔ ٹوکن کو JavaScript متغیر میں ہر VU کے لیے مقامی طور پر اسٹور کریں۔

stages اور scenarios میں کیا فرق ہے؟

stages ایک واحد ramping-vus منظر نامے کے لیے ایک شارٹ ہینڈ ہے — سادہ ریمپ اپ/ہولڈ/ریمپ-ڈاؤن پیٹرن کے لیے اچھا ہے۔ scenarios آپ کو مکمل کنٹرول فراہم کرتا ہے: متعدد بیک وقت ٹریفک کے پیٹرن، مختلف ایگزیکیوٹرز فی منظر، فی منظر کی حد، اور میٹرکس میں منظر نامے کی ٹیگنگ۔ حقیقت پسندانہ ملٹی پیٹرن ٹیسٹنگ کے لیے scenarios استعمال کریں (بیس لائن + اسپائک + سوک بیک وقت)۔

ایک واحد k6 پراسیس کتنے VUs کو سنبھال سکتا ہے؟

جدید ہارڈ ویئر پر ایک واحد k6 عمل 5,000-10,000 VUs کو برقرار رکھ سکتا ہے اور اسکرپٹ کی پیچیدگی اور رسپانس سائز کے لحاظ سے 50,000-100,000 RPS پیدا کر سکتا ہے۔ زیادہ بوجھ کے لیے، k6 cloud استعمال کریں یا لوڈ ڈسٹری بیوٹر کے پیچھے متعدد k6 مثالیں چلائیں۔ ہر VU انتہائی ہلکا ہوتا ہے (ایک گوروٹین) — k6 مساوی VU شمار کے لیے JMeter یا Gatling کے مقابلے میں بہت کم وسائل استعمال کرتا ہے۔


اگلے اقدامات

لوڈ ٹیسٹنگ آپ کی ایپلیکیشن کی حقیقی کارکردگی کے پروفائل کو ظاہر کرتی ہے اس سے پہلے کہ صارفین اسے دباؤ میں دریافت کریں۔ اس گائیڈ کے پیٹرن — ریمپنگ منظرنامے، حسب ضرورت حد، CI انضمام، اور گرافانا ڈیش بورڈز — آپ کو کارکردگی کی رکاوٹوں کو تلاش کرنے اور ان کو مستقل طور پر دور کرنے کے لیے بنیادی ڈھانچہ فراہم کرتے ہیں۔

ECOSIRE ہوم پیج، API اینڈ پوائنٹس، چیک آؤٹ فلو، اور مکمل سائٹ میپ کرالز کو کور کرنے والے k6 لوڈ ٹیسٹ کے ساتھ کارکردگی کی توثیق شدہ NestJS APIs بناتا ہے۔ ہماری بیک اینڈ انجینئرنگ سروسز کو دریافت کریں یہ جاننے کے لیے کہ ہم پہلے دن سے کارکردگی کے لیے کیسے تعمیر کرتے ہیں۔

E

تحریر

ECOSIRE Research and Development Team

ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔

Chat on WhatsApp