Load Testing & Capacity Planning
k6 Load Test Template
import http from 'k6/http'
import { check, sleep } from 'k6'
export const options = {
stages: [
{ duration: '30s', target: 50 }, // Ramp to 50 users
{ duration: '1m', target: 50 }, // Hold at 50
{ duration: '30s', target: 200 }, // Spike to 200
{ duration: '1m', target: 200 }, // Hold at 200
{ duration: '30s', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% under 500ms
http_req_failed: ['rate<0.01'], // <1% error rate
},
}
export default function () {
const res = http.get('https://agentarena.com/api/challenges')
check(res, {
'status 200': (r) => r.status === 200,
'fast response': (r) => r.timings.duration < 500,
})
sleep(1)
}
What to Test (Arena)
| Endpoint |
Load |
Threshold |
Why |
| GET /api/challenges |
200 concurrent |
p95 < 300ms |
Most-hit endpoint |
| POST /api/entries |
50 concurrent |
p95 < 1s |
Challenge start spike |
| GET /api/leaderboard/:class |
500 concurrent |
p95 < 200ms |
Spectator load |
| POST /api/votes |
100 concurrent |
p95 < 500ms |
Voting period spike |
| WebSocket spectator |
1000 connections |
Connect < 2s |
Championship events |
| Supabase Realtime |
500 subscriptions |
Event delivery < 200ms |
Live leaderboard |
| Edge Function (judge) |
20 concurrent |
Complete < 60s |
Parallel judging |
Capacity Planning Process
- Measure baseline: Run k6 at current expected load. Record p50, p95, p99, error rate.
- Identify bottleneck: What breaks first?
- DB connections maxed → add connection pooling
- p99 spikes → specific slow query → add index or cache
- Error rate rises → rate limiting hit → batch or queue
- Memory grows → leak or unbounded cache → add limits
- Extrapolate: If 50 users → 200ms p95, what about 500 users?
- IO-bound (DB queries): roughly linear → 500 users ≈ 2000ms unless cached
- CPU-bound (computation): may plateau or spike
- Fix the bottleneck before scaling further.
Performance Budgets
| Category |
Target |
Alert If |
| Page load (LCP) |
< 2.5s |
> 4s |
| API read response |
< 500ms p95 |
> 1s |
| API write response |
< 1s p95 |
> 3s |
| Database query |
< 100ms p95 |
> 500ms |
| Realtime delivery |
< 200ms |
> 1s |
| AI judge response |
< 30s |
> 60s |
| WebSocket connect |
< 2s |
> 5s |
Running Tests
# Install k6
brew install k6 # or download from grafana/k6
# Run test
k6 run tests/load/challenges.js
# Run with custom VUs and duration
k6 run --vus 100 --duration 2m tests/load/challenges.js
# Output to JSON for analysis
k6 run --out json=results.json tests/load/challenges.js
When to Load Test
- Before launch: establish baseline, find breaking points
- Before major events: championship weekends, sponsored challenges
- After architecture changes: new caching layer, database migration, new index
- Monthly: verify performance hasn't regressed
Sources
- grafana/k6 documentation and examples
- Vercel serverless performance characteristics
- Supabase connection limits and performance docs
Changelog
- 2026-03-21: Initial skill — load testing and capacity planning
1---2name: load-testing-and-capacity3description: k6 load testing, capacity planning, bottleneck identification, and performance budgets for production systems.4---56# Load Testing & Capacity Planning78## k6 Load Test Template910```js11import http from 'k6/http'12import { check, sleep } from 'k6'1314export const options = {15 stages: [16 { duration: '30s', target: 50 }, // Ramp to 50 users17 { duration: '1m', target: 50 }, // Hold at 5018 { duration: '30s', target: 200 }, // Spike to 20019 { duration: '1m', target: 200 }, // Hold at 20020 { duration: '30s', target: 0 }, // Ramp down21 ],22 thresholds: {23 http_req_duration: ['p(95)<500'], // 95% under 500ms24 http_req_failed: ['rate<0.01'], // <1% error rate25 },26}2728export default function () {29 const res = http.get('https://agentarena.com/api/challenges')30 check(res, {31 'status 200': (r) => r.status === 200,32 'fast response': (r) => r.timings.duration < 500,33 })34 sleep(1)35}36```3738## What to Test (Arena)3940| Endpoint | Load | Threshold | Why |41|----------|------|-----------|-----|42| GET /api/challenges | 200 concurrent | p95 < 300ms | Most-hit endpoint |43| POST /api/entries | 50 concurrent | p95 < 1s | Challenge start spike |44| GET /api/leaderboard/:class | 500 concurrent | p95 < 200ms | Spectator load |45| POST /api/votes | 100 concurrent | p95 < 500ms | Voting period spike |46| WebSocket spectator | 1000 connections | Connect < 2s | Championship events |47| Supabase Realtime | 500 subscriptions | Event delivery < 200ms | Live leaderboard |48| Edge Function (judge) | 20 concurrent | Complete < 60s | Parallel judging |4950## Capacity Planning Process51521. **Measure baseline:** Run k6 at current expected load. Record p50, p95, p99, error rate.532. **Identify bottleneck:** What breaks first?54 - DB connections maxed → add connection pooling55 - p99 spikes → specific slow query → add index or cache56 - Error rate rises → rate limiting hit → batch or queue57 - Memory grows → leak or unbounded cache → add limits583. **Extrapolate:** If 50 users → 200ms p95, what about 500 users?59 - IO-bound (DB queries): roughly linear → 500 users ≈ 2000ms unless cached60 - CPU-bound (computation): may plateau or spike614. **Fix the bottleneck** before scaling further.6263## Performance Budgets6465| Category | Target | Alert If |66|----------|--------|----------|67| Page load (LCP) | < 2.5s | > 4s |68| API read response | < 500ms p95 | > 1s |69| API write response | < 1s p95 | > 3s |70| Database query | < 100ms p95 | > 500ms |71| Realtime delivery | < 200ms | > 1s |72| AI judge response | < 30s | > 60s |73| WebSocket connect | < 2s | > 5s |7475## Running Tests7677```bash78# Install k679brew install k6 # or download from grafana/k68081# Run test82k6 run tests/load/challenges.js8384# Run with custom VUs and duration85k6 run --vus 100 --duration 2m tests/load/challenges.js8687# Output to JSON for analysis88k6 run --out json=results.json tests/load/challenges.js89```9091## When to Load Test9293- **Before launch:** establish baseline, find breaking points94- **Before major events:** championship weekends, sponsored challenges95- **After architecture changes:** new caching layer, database migration, new index96- **Monthly:** verify performance hasn't regressed9798## Sources99- grafana/k6 documentation and examples100- Vercel serverless performance characteristics101- Supabase connection limits and performance docs102103## Changelog104- 2026-03-21: Initial skill — load testing and capacity planning