算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
backend-load-testing【设计与创意】Use this skill when the user says 'load test', 'stress test', 'soak test', 'spike test', 'k6', 'Locust', 'Artillery', 'performance test', 'benchmark', 'throughput', 'RPS', 'latency p99', 'CI performance', 'regression test', 'load test results', or when planning performance verification. This skill enforces consistent load testing patterns: tool selec…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Design consistent, production-grade load testing plans. Every test must follow the same conventions for test type selection, scenario design, metrics collection, threshold definition, results analysis, and CI regression detection.
Exact user phrases: "load test", "stress test", "soak test", "spike test", "k6", "Locust", "Artillery", "performance test", "benchmark", "throughput", "RPS", "latency p99", "CI performance", "regression test", "load test results", "run a load test", "design a load test".
Before activating, verify:
No file output unless the user requests it. Produces load test specifications and scripts as text.
For each test:
Test: {name}
Type: {smoke | load | stress | soak | spike}
Tool: {k6 | Locust | Artillery}
Target: {endpoint or system under test}
Duration: {time}
VUs: {number} / RPS: {target}
Thresholds:
p95 < {ms}
p99 < {ms}
error_rate < {%}
For a full test plan:
## {test suite name}
{list of tests with scenarios}
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
Per test scenario: 10 lines. Per test plan: unlimited.
What do you need to validate?
├── Script works, no config issues
│ └── Smoke test — 1-2 min, 1-5 VUs
├── Handles expected traffic within SLOs
│ └── Load test — 10-30 min, target concurrent users
├── Find breaking point and bottleneck
│ └── Stress test — 10-20 min, ramp until failure
├── Memory leaks / degradation over time
│ └── Soak test — 2-24 hours, 80% of peak
└── Auto-scaling / burst handling
└── Spike test — 5-10 min, sudden 2-10x increase
What stack do you use?
├── Any / no preference
│ └── k6 — JS scripting, high perf, Go engine, CI-friendly
├── Python team
│ └── Locust — Python scripting, distributed, real-time Web UI
├── Node.js team, WebSocket testing
│ └── Artillery — YAML/JS, WS/Socket.io, Lambda support
└── Need protocol-level (not HTTP)
└── ghz (gRPC) / wrk / hey (raw HTTP) / JMeter (complex scenarios)
k6: JS scripting, high performance, Go engine, built-in metrics, Grafana Cloud
Best for: most projects, CI/CD, threshold assertions
Locust: Python scripting, distributed by default, web UI
Best for: Python teams, complex workflows, real-time monitoring
Artillery: YAML/JS, Node.js, Lambda support, HTTP/WebSocket/Socket.io
Best for: Node.js teams, WebSocket testing, serverless deployments
Smoke test:
Duration: 1-2 min. VUs: 1-5.
Purpose: verify test script works, no config issues.
Load test:
Duration: 10-30 min. VUs: target concurrent users.
Purpose: verify system handles expected traffic within SLOs.
Stress test:
Duration: 10-20 min. VUs: ramp up until failure.
Purpose: find breaking point, identify bottlenecks.
Soak test:
Duration: 2-24 hours. VUs: 80% of expected peak.
Purpose: detect memory leaks, DB connection leaks, degradation over time.
Spike test:
Duration: 5-10 min. VUs: sudden 2-10x increase.
Purpose: test auto-scaling, burst handling, queue draining.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const failures = new Rate('failed_requests');
const loginDuration = new Trend('login_duration');
export const options = {
stages: [
{ duration: '2m', target: 100 }, // ramp up
{ duration: '5m', target: 100 }, // steady
{ duration: '2m', target: 200 }, // ramp up
{ duration: '5m', target: 200 }, // steady
{ duration: '1m', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
http_req_failed: ['rate<0.01'],
failed_requests: ['rate<0.05'],
},
};
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
export default function () {
const payload = JSON.stringify({ email: 'test@test.com', password: '123456' });
const params = { headers: { 'Content-Type': 'application/json' } };
const res = http.post(`${BASE_URL}/api/v1/auth/login`, payload, params);
check(res, { 'status is 200': (r) => r.status === 200 });
failures.add(res.status !== 200);
loginDuration.add(res.timings.duration);
sleep(1);
}
Test multiple user journeys in one script:
import { group } from 'k6';
export const options = {
scenarios: {
browse: {
executor: 'constant-vus',
vus: 50,
duration: '10m',
exec: 'browseScenario',
startTime: '0s',
},
checkout: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 10 },
{ duration: '5m', target: 10 },
{ duration: '1m', target: 0 },
],
exec: 'checkoutScenario',
startTime: '30s',
},
},
};
export function browseScenario() {
group('browse products', () => {
http.get(`${BASE_URL}/api/products`);
sleep(3);
http.get(`${BASE_URL}/api/products/123`);
sleep(2);
});
}
export function checkoutScenario() {
group('complete purchase', () => {
const token = login();
const cart = addToCart(token);
const order = checkout(token, cart);
check(order, { 'order confirmed': (r) => r.status === 200 });
sleep(5);
});
}
from locust import HttpUser, task, between
from uuid import uuid4
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
def on_start(self):
"""Login on start and store token"""
resp = self.client.post("/api/v1/auth/login", json={
"email": f"user-{uuid4()}@test.com",
"password": "test123"
})
self.token = resp.json().get("token", "")
@task(3)
def browse_products(self):
self.client.get("/api/v1/products",
headers={"Authorization": f"Bearer {self.token}"})
@task(1)
def create_order(self):
self.client.post("/api/v1/orders",
json={"productId": "p1", "quantity": 1},
headers={"Authorization": f"Bearer {self.token}"})
@task(1)
def view_profile(self):
self.client.get("/api/v1/users/me",
headers={"Authorization": f"Bearer {self.token}"})
Essential metrics:
- http_req_duration (p50, p95, p99, max)
- http_req_failed (error rate %)
- http_reqs (throughput — RPS)
- vus (concurrent virtual users)
- iteration_duration (full scenario time)
Typical thresholds:
p95 < 500ms → Good
p95 < 1000ms → Acceptable
p99 < 2000ms → SLO boundary
error_rate < 1% → Acceptable failure rate
Resource metrics (track alongside):
- CPU utilization
- Memory usage
- DB connection pool usage
- GC pauses (for JVM/Go/Erlang)
Static data: pre-generated CSV of users, products, etc.
k6: SharedArray or CSV import for realistic data per VU
Dynamic data: create resources during setup phase (setup() function)
Tear down after test (teardown() function)
Data isolation: use unique prefixes per test run to avoid collisions
e.g., "test-user-{runId}-{vuId}"
// k6 — CSV data loading
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
const users = new SharedArray('users', function () {
return papaparse.parse(open('./test-users.csv'), { header: true }).data;
});
export default function () {
const user = users[__VU % users.length];
http.post(`${BASE_URL}/api/v1/auth/login`, JSON.stringify(user), params);
}
Summary output:
- Pass/fail per threshold
- p50, p95, p99, max latency
- RPS average and peak
- Error count and rate
- Checks passed/failed
Interpretation:
- Latency increases with VUs → scaling issue (blocked on DB, CPU, or lock)
- Error rate increases → system reaching capacity
- p99 >> p95 → tail latency outliers (GC pauses, queue buildup)
- Decreasing RPS at high VUs → contention or resource exhaustion
# GitHub Actions — k6 load test
name: Load Test
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
load-test:
runs-on: ubuntu-latest
services:
app:
image: ghcr.io/myorg/app:latest
ports: [3000:3000]
steps:
- uses: actions/checkout@v4
- name: Run k6 load test
uses: grafana/k6-action@v0.3.1
with:
filename: tests/load/smoke-test.js
flags: --env BASE_URL=http://localhost:3000
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: k6-report
path: k6-report.html
| Concern | Practice |
|---|---|
| Production vs staging | Never load test production directly. Use dedicated staging or preview env. Exception: canary with 1-2% traffic |
| Data isolation | Generate unique test data per run. Never reuse or share data between runs |
| Monitoring | Watch server-side metrics: CPU, memory, DB pool, GC, network I/O |
| Environment parity | Same instance size, same replicas, same DB config as production |
| Warm-up | Allow 1-2 min ramp-up before measuring steady state (JIT compilation, connection pools) |
| Tear-down | Clean up test data after completion (or use ephemeral environments) |
| Concern | Practice |
|---|---|
| Data contamination | Never use real user data in load tests. Generate synthetic data |
| Credential rotation | Rotate test credentials before production-facing exposure |
| Rate limit bypass | Get explicit exemption from rate limiting for load test accounts/IPs |
| Network isolation | Run load tests in isolated VPC/network segment |
| DoS concern | Coordinate with infra team before high-volume tests (>10K RPS) |
| Anti-Pattern | Why It's Bad | Fix |
|---|---|---|
| Testing only the health endpoint | Does not reflect real traffic patterns | Test critical user journeys |
| No ramp-up period | Cold start skews results (JIT, connection pools) | Include 1-2 min ramp-up |
| No think time | Overestimates system load (no real user is that fast) | Add realistic sleep/wait times |
| Ignoring server-side metrics | Client metrics alone miss root cause | Watch CPU, memory, DB, GC |
| Single-user test data | Lock contention, cache effects unrealistic | Use unique data per VU |
| Running once, trusting results | Variance due to GC, cache warmup | Run 3+ times, take median |
| No thresholds | A test without pass/fail criteria is a benchmark, not a test | Always set thresholds |
// Custom metrics
import { Trend, Rate, Counter, Gauge } from 'k6/metrics';
const businessErrors = new Rate('business_errors');
const checkoutLatency = new Trend('checkout_latency');
// Checks vs thresholds
export default function () {
const res = http.get('http://test.k6.io');
const passed = check(res, {
'status is 200': (r) => r.status === 200,
'body is not empty': (r) => r.body.length > 0,
});
if (!passed) businessErrors.add(1);
}
// gRPC testing with k6
import grpc from 'k6/net/grpc';
const client = new grpc.Client();
client.load(['definitions'], 'user_service.proto');
export default function () {
client.connect('localhost:50051', { plaintext: true });
const response = client.invoke('users.UserService/GetUser', { userId: '123' });
check(response, { 'status is OK': (r) => r.status === grpc.StatusOK });
client.close();
}
No artifact produced unless requested. Next skill: backend-caching — if the load test reveals cache-related bottlenecks. Next skill: backend-rate-limiting — if the load test needs traffic shaping. Carry forward: test scenarios, thresholds, data strategy, CI config.
` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).