본문으로 건너뛰기

성능 최적화 실전 고수 팁

캐시·압축·HTTP/2 설정이 끝났다고 끝이 아닙니다. 실제로 얼마나 빨라졌는지 부하 테스트로 검증하고, 병목 지점을 정확히 찾아야 진정한 성능 최적화가 완성됩니다.


부하 테스트 도구 비교

도구특징프로토콜권장 용도
ab (Apache Bench)단순, 빠른 설치HTTP/1.1빠른 기준선 측정
wrk고성능, Lua 스크립트HTTP/1.1처리량·지연 측정
k6JS 시나리오, 시각화HTTP/1.1~2시나리오 부하 테스트
h2loadHTTP/2 전용HTTP/2HTTP/2 성능 검증
locustPython, 분산 가능HTTP복잡한 사용자 시나리오

ab (Apache Bench)

# 기본 사용법
# -n: 총 요청 수, -c: 동시 연결 수, -k: Keep-Alive 활성화
ab -n 10000 -c 100 -k https://example.com/api/products

# 출력 해석
# Requests per second: 1234.56 [#/sec] ← 처리량 (높을수록 좋음)
# Time per request: 81.00 [ms] ← 평균 응답 시간
# Time per request: 0.81 [ms] ← 동시 요청 기준 평균
# Transfer rate: 456.78 [Kbytes/sec]

# Percentile 분포 (p99 > 1000ms면 개선 필요)
# 50% 45 ms
# 75% 78 ms
# 90% 120 ms
# 95% 180 ms
# 99% 450 ms
# 100% 1200 ms (longest request)

# POST 요청 테스트
ab -n 5000 -c 50 -k \
-H "Content-Type: application/json" \
-p /tmp/payload.json \
https://example.com/api/orders

wrk — 고성능 벤치마크

# 설치
sudo apt install wrk # 또는 소스 빌드

# 기본 사용법
# -t: 스레드 수, -c: 연결 수, -d: 지속 시간
wrk -t4 -c100 -d30s https://example.com/api/products
# Running 30s test @ https://example.com/api/products
# 4 threads and 100 connections
#
# Thread Stats Avg Stdev Max +/- Stdev
# Latency 45.67ms 12.34ms 234.56ms 87.65%
# Req/Sec 567.89 45.67 678.90 78.90%
# 68145 requests in 30s, 12.34MB read
# Requests/sec: 2271.50 ← 처리량
# Latency: 45ms ← 평균 응답 시간

# Lua 스크립트로 POST 요청
cat > /tmp/post.lua << 'EOF'
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"userId": 1, "productId": 100, "qty": 2}'
EOF

wrk -t4 -c50 -d30s -s /tmp/post.lua https://example.com/api/orders

# 연결별 응답 시간 분포
wrk -t4 -c100 -d30s --latency https://example.com/

k6 — 시나리오 기반 부하 테스트

# 설치
sudo apt install k6
# 또는: https://k6.io/docs/getting-started/installation/
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const errorRate = new Rate('errors');

// 테스트 시나리오 정의
export const options = {
stages: [
{ duration: '1m', target: 50 }, // 1분간 50명으로 램프업
{ duration: '3m', target: 200 }, // 3분간 200명 유지
{ duration: '1m', target: 0 }, // 1분간 종료
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%가 500ms 이내
errors: ['rate<0.01'], // 에러율 1% 미만
},
};

export default function () {
// 상품 목록 조회
const res = http.get('https://example.com/api/products', {
headers: { 'Accept': 'application/json' },
});

check(res, {
'status 200': (r) => r.status === 200,
'response < 500ms': (r) => r.timings.duration < 500,
});

errorRate.add(res.status !== 200);

sleep(1);
}
# 실행
k6 run load-test.js

# 결과 출력
# ✓ status 200
# ✓ response < 500ms
#
# checks.........................: 99.80% ✓ 5988 ✗ 12
# http_req_duration..............: avg=87.23ms min=12.34ms med=71.45ms
# max=1.23s p(90)=156.78ms p(95)=234.56ms
# http_reqs......................: 6000 199.89/s

# Grafana 연동으로 실시간 시각화
k6 run --out influxdb=http://localhost:8086/k6 load-test.js

h2load — HTTP/2 성능 측정

# 설치 (nghttp2 패키지)
sudo apt install nghttp2-client

# HTTP/2 부하 테스트
# -n: 총 요청, -c: 연결 수, -m: 연결당 동시 스트림
h2load -n 10000 -c 50 -m 10 https://example.com/api/products

# 결과
# finished in 5.23s, 1912.04 req/s, 2.34MB/s
# requests: 10000 total, 10000 started, 10000 done, 10000 succeeded
# status codes: 10000 2xx, 0 3xx, 0 4xx, 0 5xx
# traffic: 12.23MB total, 1.23MB headers, 10.78MB data
#
# time for request: min=5ms max=234ms mean=52ms sd=23ms cv=44.23%

병목 지점 분석 방법론

1단계: 응답 시간 분해

# curl으로 각 단계 시간 측정
curl -o /dev/null -s -w "
DNS: %{time_namelookup}s
TCP Connect: %{time_connect}s
TLS: %{time_appconnect}s
TTFB: %{time_starttransfer}s ← Time to First Byte
Total: %{time_total}s
Size: %{size_download} bytes
" https://example.com/api/products

2단계: 서버별 처리 시간 분리

# Nginx upstream_response_time 로깅
log_format perf '$remote_addr [$time_local] '
'"$request" $status '
'req=$request_time ' # Nginx 총 처리 시간
'upstream=$upstream_response_time ' # 백엔드 처리 시간
'cache=$upstream_cache_status';

# Nginx 처리 시간 = 총 시간 - upstream 시간
# 이 차이가 크면 Nginx 자체가 병목 (캐시, 압축 설정 확인)

3단계: 병목 위치 판단

응답이 느린 경우 체크리스트:
──────────────────────────────────────
1. DNS 조회 느림? → DNS 캐시 TTL 확인
2. TCP/TLS 느림? → Keep-Alive 활성화, TLS 세션 캐시 확인
3. TTFB 느림? → 백엔드 로직, DB 쿼리 확인
4. 전송 느림? → 응답 크기 (압축 여부), 네트워크 대역폭 확인
──────────────────────────────────────

TTFB가 느린 경우 (백엔드 병목):
- DB 슬로우 쿼리 확인: EXPLAIN ANALYZE
- N+1 쿼리 문제 확인
- 외부 API 호출 타임아웃 미설정
- 스레드 풀 고갈 (Tomcat maxThreads 초과)

전송이 느린 경우 (네트워크/크기 병목):
- 압축 미설정 (gzip/brotli 확인)
- 캐시 미적용 (X-Cache-Status: MISS)
- 응답 페이로드 과다 (페이지네이션 확인)

성능 지표 목표값 (SLO 기준)

지표목표값개선 방법
TTFB< 200ms캐시, DB 쿼리 최적화
p95 응답 시간< 500ms스레드 풀 튜닝, 캐시
p99 응답 시간< 1000ms타임아웃 설정, 서킷 브레이커
에러율< 0.1%헬스체크, 자동 재시도
압축률> 60% (텍스트)gzip/brotli 설정 확인
캐시 히트율> 80%proxy_cache 설정 최적화

성능 테스트 자동화 (CI/CD 통합)

#!/bin/bash
# performance-gate.sh — 배포 전 성능 기준 통과 확인

ENDPOINT="https://staging.example.com/api/products"
MAX_P95_MS=500
MIN_RPS=100

echo "=== Performance Gate Check ==="

# wrk으로 30초 부하 테스트
result=$(wrk -t4 -c100 -d30s --latency "$ENDPOINT" 2>&1)

# p95 추출
p95=$(echo "$result" | grep "99%" | awk '{print $2}' | sed 's/ms//')
rps=$(echo "$result" | grep "Requests/sec" | awk '{print $2}')

echo "p95: ${p95}ms (기준: ${MAX_P95_MS}ms)"
echo "RPS: ${rps} (기준: ${MIN_RPS})"

if (( $(echo "$p95 > $MAX_P95_MS" | bc -l) )); then
echo "❌ 성능 기준 미달: p95 ${p95}ms > ${MAX_P95_MS}ms"
exit 1
fi

echo "✅ 성능 기준 통과"