[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-10 22:08:15 +09:00
parent 21ac3ed255
commit 504fd5fb42
3011 changed files with 380280 additions and 206977 deletions
@@ -0,0 +1,389 @@
---
id: testing-load-k6-locust
title: Load Testing — k6 / Locust / Artillery
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [testing, load, performance, vibe-coding]
tech_stack: { language: "JS / Python", applicable_to: ["Backend"] }
applied_in: []
aliases: [load testing, k6, Locust, Artillery, JMeter, Gatling, capacity, soak test]
---
# Load Testing
> Production 의 load 견디는지 검증. **k6 (modern), Locust (Python), Artillery, JMeter, Gatling**. Smoke / load / stress / soak / spike.
## 📖 핵심 개념
- Load test ≠ stress test ≠ soak test.
- VU (virtual user) = 동시 사용자.
- RPS = req per sec.
- p95/p99 latency 가 mean 보다 중요.
## 💻 코드 패턴
### k6 (modern, Go)
```js
// load.js
import http from 'k6/http';
import { sleep, check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 100 }, // ramp up
{ duration: '5m', target: 100 }, // steady
{ duration: '30s', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const r = http.get('https://api.example.com/users');
check(r, { 'status 200': (r) => r.status === 200 });
sleep(1);
}
```
```bash
k6 run load.js
```
### Output
```
✓ status 200
http_req_duration..: p95=412ms p99=890ms
http_req_failed....: 0.05%
http_reqs..........: 30000 100/s
vus................: 100
```
### Smoke test (1-5 VU, 1 min)
```js
options = { vus: 1, duration: '1m' };
```
→ Sanity check.
### Load test (typical)
```js
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
]
```
### Stress test (capacity 한계)
```js
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 200 },
{ duration: '5m', target: 500 },
{ duration: '5m', target: 1000 },
{ duration: '5m', target: 2000 },
]
```
→ 어디서 깨지는지 발견.
### Soak test (장시간)
```js
options = {
vus: 100,
duration: '4h',
};
```
→ Memory leak / resource exhaustion 발견.
### Spike test
```js
stages: [
{ duration: '10s', target: 100 },
{ duration: '1m', target: 100 },
{ duration: '10s', target: 5000 }, // spike
{ duration: '3m', target: 5000 },
{ duration: '10s', target: 100 },
{ duration: '3m', target: 100 },
]
```
→ 갑작스런 traffic 처리?
### Auth 가진 scenario
```js
import http from 'k6/http';
import { check } from 'k6';
export function setup() {
const login = http.post('https://api.example.com/auth', JSON.stringify({
email: 'test@example.com',
password: 'pw',
}));
return { token: login.json('token') };
}
export default function (data) {
const r = http.get('https://api.example.com/users', {
headers: { Authorization: `Bearer ${data.token}` },
});
check(r, { '200': (r) => r.status === 200 });
}
```
### 다중 endpoint
```js
import { group } from 'k6';
export default function () {
group('list users', () => {
http.get('/users');
});
group('create order', () => {
http.post('/orders', JSON.stringify({ ... }));
});
}
```
### Test data
```js
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', () => JSON.parse(open('./users.json')));
export default function () {
const user = users[Math.floor(Math.random() * users.length)];
http.post('/login', JSON.stringify(user));
}
```
→ 100k user 가 1번씩 로그인 = 진짜 traffic.
### Distributed (k6 Cloud / OSS)
```bash
k6 cloud load.js
# 또는 self-host
k6 run --distributed
```
→ 1 machine = ~10k VU. 더 = distributed.
### Locust (Python)
```python
# locustfile.py
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 5)
@task(3)
def list_users(self):
self.client.get('/users')
@task(1)
def view_profile(self):
self.client.get('/profile')
def on_start(self):
self.client.post('/login', json={...})
```
```bash
locust -f locustfile.py --host=https://api.example.com
# Web UI: http://localhost:8089
```
→ Python script + UI.
### Artillery (YAML)
```yaml
# load.yml
config:
target: https://api.example.com
phases:
- duration: 60
arrivalRate: 10
- duration: 300
arrivalRate: 100
scenarios:
- flow:
- get: { url: '/users' }
- think: 1
- post: { url: '/orders', json: { ... } }
```
```bash
artillery run load.yml
```
### Gatling (Scala)
```scala
class LoadSim extends Simulation {
val httpProtocol = http.baseUrl("https://api.example.com")
val scn = scenario("Users").exec(
http("list users").get("/users")
)
setUp(scn.inject(rampUsers(100) during (30 seconds)).protocols(httpProtocol))
}
```
→ JVM. 큰 enterprise 가 사용.
### JMeter (legacy GUI)
```
- XML config
- Thread group
- Plugin ecosystem
- 큰 enterprise
→ 옛 — Gatling / k6 가 모던.
```
### CI 통합
```yaml
# .github/workflows/load.yml
- run: k6 run load.js
```
→ PR 가 load test (smoke). 매일 staging full load.
### Threshold (auto fail)
```js
thresholds: {
http_req_duration: ['p(95)<500'], // 95% < 500ms
http_req_failed: ['rate<0.01'], // 1% 이하 fail
}
```
→ k6 가 exit code != 0.
### Metrics export
```js
// k6 → InfluxDB / Prometheus / DataDog
options = {
ext: {
loadimpact: { projectID: 123 },
},
};
```
```bash
k6 run --out influxdb=http://...
```
### Backend monitoring 동시
```
Load test 중:
- App metric (CPU, memory, p99)
- DB metric (query count, lock)
- Network (latency, dropped)
- Queue (depth)
→ Bottleneck 식별.
```
### Production-like environment
```
Test env = prod env / 10 (size).
Production:
- 100 instance × 4 CPU = 400 CPU
- 1000 RPS
Test:
- 10 instance × 4 CPU = 40 CPU
- 100 RPS
→ Scale linear. 결과 외삽.
```
### Realistic load
```
Pareto: 80% read / 20% write.
실제 prod log → top endpoint % 추출.
읽기 많음 ≠ 쓰기 적음 (cache 매번 hit).
```
### Test data lifecycle
```
- Setup: 100k user 생성 (한 번)
- Test: 매번 random 추출
- Teardown: 안 — load test data 영구 (다음 가)
또는:
- 매 test = 새 DB schema (격리)
```
### Bottleneck 식별
```
RPS 늘림 → p99 latency ↑ → 어디?
1. CPU bound: app instance 가 100% CPU
2. DB bound: query 가 long, conn 다
3. Network: bandwidth 한계
4. Memory: GC 폭발
5. External: Stripe / S3 가 throttle
```
→ Profiler / APM (Datadog, NewRelic) 동시.
### Service mesh / sidecar overhead
```
Istio + Envoy = 매 hop 가 1-5ms.
- Service A → mesh → Service B
- 매 RPC 가 5-20ms 더.
→ Load test 결과 가 prod 에 가까운지 검증.
```
### Cost
```
1 hour 1000 RPS = ~3.6M req.
Cloud egress + storage = $.
→ Load test 도 budget.
```
## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| 모던 default | k6 |
| Python team | Locust |
| YAML 친화 | Artillery |
| JVM enterprise | Gatling |
| GUI 필요 | JMeter |
| Smoke test | k6 (1 VU) |
| Capacity | Stress (k6) |
| Memory leak | Soak (4h+) |
| Black Friday 대비 | Spike |
## ❌ 안티패턴
- **No load test**: prod 가 처음 진실.
- **Local machine 만 = test**: 1 machine 한계.
- **No threshold**: pass / fail 모름.
- **Spike test 안 함**: traffic burst 깨짐.
- **Soak 안 함**: 1주 후 OOM.
- **DB / cache reset 없음**: cache hit rate 가짜.
- **Realistic 아닌 mix**: 모두 read = 가짜.
## 🤖 LLM 활용 힌트
- k6 가 modern default.
- Smoke / load / stress / soak / spike = 5 종류.
- p95/p99 가 핵심 metric.
- Bottleneck 식별 = profiler 동시.
## 🔗 관련 문서
- [[Testing_Test_Pyramid]]
- [[Backend_Rate_Limiting]]
- [[Perf_Node_Profiling]]