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.mdsource 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