o2o-negosium-original/lps/loadtest/bench_multicore.sh
민헌 a63793daf0 test(lps): Locust API 부하 테스트 + 멀티코어 벤치 (풀 병목 발견·증명)
API(enqueue/조회, asyncpg I/O 바운드) 부하 테스트로 멀티코어 활용·한계 측정.

- loadtest/locustfile.py: enqueue(고유코드 write)+조회 가중 부하.
- loadtest/bench_multicore.sh: PROCESS_COUNT 1→N 자동 비교(--processes 로 부하생성기도 멀티프로세스).
- server_configs: PROCESS_COUNT / DB_POOL_SIZE / DB_MAX_OVERFLOW env override(코드·toml 수정 없이 튜닝).
- loadtest/README.md: 발견 문서화.

발견(11코어·1500users): 기본 풀(10/20)로 워커 늘리면 실패 폭증(1w=0 → 4w=10336) —
(pool+overflow)×2엔진×workers=240 > PG max_connections=100 커넥션 고갈(SQLAlchemy pool checkout 실패).
증명: 풀 8/4(96<100)로 PC=4 재실행 → RPS 1336→2705(2배), 실패 0. 코드는 멀티코어 활용 가능,
막는 건 풀 오버서브스크립션. 규칙: (pool+overflow)×2×process_count ≤ max_connections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:45:25 +09:00

70 lines
3.8 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# 멀티코어 스케일링 벤치 (대화형).
# PROCESS_COUNT 를 1→N 으로 바꿔가며 API 를 띄우고, 각각 locust 헤드리스로 RPS/지연을 측정해 비교한다.
# API 는 asyncio(스레드 1개)라 단일 프로세스=단일 코어 → workers 를 늘리면 코어만큼 스케일해야 정상.
#
# 주의:
# - **워커는 반드시 OFF** (부하 중 실제 크롤=프록시/AI 비용). 잡은 PENDING 으로 쌓임 → 끝나면 TRUNCATE.
# - 커넥션 한계: (pool_size+max_overflow)×2엔진×workers 가 PG max_connections 를 넘으면 거기서 막힌다.
set -euo pipefail
cd "$(dirname "$0")/.." # lps/
VENV=".venv"; PY="$VENV/bin/python"; LOCUST="$VENV/bin/locust"
HOST="http://localhost:9600"; PORT=9600
command -v psql >/dev/null || true
[[ -x "$LOCUST" ]] || { echo "[setup] locust 설치..."; "$PY" -m pip install -q locust; }
CORES=$("$PY" -c "import os;print(os.cpu_count())")
echo "── 멀티코어 부하 벤치 ── (CPU 코어: $CORES)"
# 워커 실행 중이면 경고
if pgrep -f worker_main.py >/dev/null; then
echo "[warn] 워커(worker_main.py)가 실행 중입니다 — 부하 중 실제 크롤 비용 발생. 중단 권장:"
read -rp " 워커를 종료할까요? [Y/n]: " k; [[ "${k:-Y}" =~ ^[Nn] ]] || pkill -f worker_main.py || true
fi
read -rp "동시 유저 수(-u) [200]: " USERS; USERS="${USERS:-200}"
read -rp "spawn rate(-r) [20]: " SPAWN; SPAWN="${SPAWN:-20}"
read -rp "각 단계 지속(-t) [45s]: " DUR; DUR="${DUR:-45s}"
read -rp "PROCESS_COUNT 목록(공백구분) [1 2 4]: " PCS; PCS="${PCS:-1 2 4}"
# PG max_connections (참고)
MAXC=$(psql -h 127.0.0.1 -U postgres -tAc "SHOW max_connections;" 2>/dev/null || echo "?")
echo "PG max_connections=$MAXC · 풀 추정=(pool+overflow)×2×workers"
echo ""
RESULTS=()
for PC in $PCS; do
# 기존 API 종료
lsof -ti:"$PORT" 2>/dev/null | xargs kill 2>/dev/null || true; sleep 1
echo "▶ PROCESS_COUNT=$PC 로 API 기동..."
PROCESS_COUNT="$PC" APP_ENV=local "$PY" web_main.py > "/tmp/lps_api_pc${PC}.log" 2>&1 &
APIPID=$!
# 기동 대기(헬스)
for _ in $(seq 1 20); do curl -s "$HOST/healthz" >/dev/null 2>&1 && break; sleep 1; done
# locust 헤드리스 (--processes 로 부하 생성기도 멀티프로세스 → locust 가 병목되지 않게)
"$LOCUST" -f loadtest/locustfile.py --host "$HOST" --headless --processes "${LOCUST_PROCESSES:-4}" \
-u "$USERS" -r "$SPAWN" -t "$DUR" --csv "/tmp/lps_locust_pc${PC}" --only-summary >/dev/null 2>&1 || true
kill "$APIPID" 2>/dev/null || true; sleep 1
# Aggregated 행 파싱: $10=Req/s $17=95% $3=count $4=fail
read -r RPS P95 CNT FAIL < <(awk -F, '$2=="Aggregated"{print $10, $17, $3, $4}' "/tmp/lps_locust_pc${PC}_stats.csv" 2>/dev/null || echo "0 0 0 0")
RESULTS+=("$PC|${RPS:-0}|${P95:-0}|${CNT:-0}|${FAIL:-0}")
printf " → RPS=%.0f p95=%sms 요청=%s 실패=%s\n\n" "${RPS:-0}" "${P95:-0}" "${CNT:-0}" "${FAIL:-0}"
done
echo "════════ 결과 (동시유저 $USERS, $DUR) ════════"
printf "%-8s %-10s %-10s %-10s %-8s %-10s\n" "workers" "RPS" "p95(ms)" "요청" "실패" "vs 1코어"
BASE=""
for r in "${RESULTS[@]}"; do
IFS='|' read -r pc rps p95 cnt fail <<< "$r"
[[ -z "$BASE" ]] && BASE="$rps"
SCALE=$("$PY" -c "print(f'{(${rps:-0}/${BASE:-1}):.2f}x')" 2>/dev/null || echo "-")
printf "%-8s %-10.0f %-10s %-10s %-8s %-10s\n" "$pc" "${rps:-0}" "${p95:-0}" "${cnt:-0}" "${fail:-0}" "$SCALE"
done
echo ""
echo "해석: RPS 가 workers 에 비례해 오르면 멀티코어 활용 정상. 어느 지점부터 안 오르고 실패가 늘면"
echo " 거기가 한계 — 보통 DB 커넥션(max_connections=$MAXC) 또는 write 경합. 풀/PG 튜닝 대상."
echo "정리: psql -h 127.0.0.1 -U postgres -d lps_db -c 'TRUNCATE job;' (쌓인 부하 잡 제거)"