Vibecosystem load-testing-patterns

k6 script templates, load profiles, response time thresholds, SLO validation, and performance testing strategies.

install
source · Clone the upstream repo
git clone https://github.com/vibeeval/vibecosystem
manifest: skills/load-testing-patterns/skill.md
source content

Load Testing Patterns

Performance validation with k6 for SLO-driven load testing.

Basic k6 Script Template

import http from 'k6/http'
import { check, sleep } from 'k6'
import { Rate, Trend } from 'k6/metrics'

// Custom metrics
const errorRate = new Rate('errors')
const loginDuration = new Trend('login_duration')

// Thresholds define pass/fail criteria
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],  // 95th < 500ms, 99th < 1s
    http_req_failed: ['rate<0.01'],                    // Error rate < 1%
    errors: ['rate<0.05'],                             // Custom error rate < 5%
  },
  scenarios: {
    smoke: {
      executor: 'constant-vus',
      vus: 5,
      duration: '1m',
    }
  }
}

export default function () {
  const res = http.get('https://api.example.com/health')

  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
    'body has status': (r) => JSON.parse(r.body).status === 'ok',
  }) || errorRate.add(1)

  sleep(1)
}

Load Profiles

export const options = {
  scenarios: {
    // 1. Smoke Test: verify system works under minimal load
    smoke: {
      executor: 'constant-vus',
      vus: 3,
      duration: '1m',
    },

    // 2. Load Test: normal expected traffic
    load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 50 },   // Ramp up
        { duration: '5m', target: 50 },   // Steady state
        { duration: '2m', target: 0 },    // Ramp down
      ],
    },

    // 3. Stress Test: find breaking point
    stress: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '2m', target: 100 },
        { duration: '5m', target: 100 },
        { duration: '2m', target: 200 },  // Beyond normal
        { duration: '5m', target: 200 },
        { duration: '2m', target: 300 },  // Breaking point?
        { duration: '5m', target: 300 },
        { duration: '5m', target: 0 },
      ],
    },

    // 4. Spike Test: sudden traffic surge
    spike: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '30s', target: 10 },
        { duration: '10s', target: 500 },  // Instant spike
        { duration: '1m', target: 500 },
        { duration: '10s', target: 10 },   // Instant drop
        { duration: '1m', target: 10 },
      ],
    },

    // 5. Soak Test: sustained load over time (memory leaks, connection exhaustion)
    soak: {
      executor: 'constant-vus',
      vus: 50,
      duration: '2h',
    },
  }
}

Realistic User Scenarios

import http from 'k6/http'
import { check, group, sleep } from 'k6'

const BASE_URL = __ENV.BASE_URL || 'https://api.example.com'

export default function () {
  // Simulate real user journey, not isolated endpoints
  let token

  group('01_login', () => {
    const res = http.post(`${BASE_URL}/auth/login`, JSON.stringify({
      email: `user${__VU}@test.com`,
      password: 'testpass123'
    }), { headers: { 'Content-Type': 'application/json' } })

    check(res, { 'login successful': (r) => r.status === 200 })
    token = JSON.parse(res.body).token
  })

  sleep(Math.random() * 3 + 1)  // Think time: 1-4 seconds

  group('02_browse_products', () => {
    const headers = { Authorization: `Bearer ${token}` }

    const listRes = http.get(`${BASE_URL}/products?page=1&limit=20`, { headers })
    check(listRes, { 'products loaded': (r) => r.status === 200 })

    const products = JSON.parse(listRes.body).data
    if (products.length > 0) {
      const product = products[Math.floor(Math.random() * products.length)]
      const detailRes = http.get(`${BASE_URL}/products/${product.id}`, { headers })
      check(detailRes, { 'product detail loaded': (r) => r.status === 200 })
    }
  })

  sleep(Math.random() * 2 + 1)

  group('03_add_to_cart', () => {
    const res = http.post(`${BASE_URL}/cart/items`, JSON.stringify({
      productId: 'prod_001',
      quantity: 1
    }), {
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`
      }
    })
    check(res, { 'item added to cart': (r) => r.status === 201 })
  })
}

SLO Validation

export const options = {
  thresholds: {
    // SLO: Availability > 99.9%
    http_req_failed: ['rate<0.001'],

    // SLO: p50 < 100ms, p95 < 500ms, p99 < 1000ms
    http_req_duration: [
      'p(50)<100',
      'p(95)<500',
      'p(99)<1000',
    ],

    // SLO per endpoint using tags
    'http_req_duration{name:login}': ['p(95)<800'],
    'http_req_duration{name:get_products}': ['p(95)<200'],
    'http_req_duration{name:checkout}': ['p(95)<2000'],

    // SLO: Throughput > 1000 RPS
    http_reqs: ['rate>1000'],
  }
}

// Tag requests for per-endpoint SLOs
export default function () {
  http.get(`${BASE_URL}/products`, { tags: { name: 'get_products' } })
  http.post(`${BASE_URL}/auth/login`, payload, { tags: { name: 'login' } })
}

CI Integration

# .github/workflows/load-test.yml
name: Load Test
on:
  pull_request:
    branches: [main]

jobs:
  load-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/load/smoke.js
        env:
          BASE_URL: ${{ secrets.STAGING_URL }}

      # k6 exits non-zero if thresholds fail → PR check fails

Checklist

  • Run smoke test on every PR (fast, catches regressions)
  • Load test weekly against staging with production-like data
  • Stress test quarterly to find breaking points
  • Soak test before major releases (2+ hours, detect memory leaks)
  • Thresholds set per endpoint, not just global p95
  • Realistic think times between requests (sleep 1-5s)
  • Use multiple VU scenarios (not everyone does the same thing)
  • Store results in Grafana/InfluxDB for trend analysis

Anti-Patterns

  • Testing only happy paths: include error scenarios (404, 429, 500)
  • No think time: unrealistic request rate, not how users behave
  • Testing against production without traffic control (use staging)
  • Single endpoint tests: real users hit multiple endpoints per session
  • Ignoring connection time: p95 duration hides DNS/TLS overhead
  • Hardcoded test data: VUs sharing same user account causes contention