feat(lps): 로컬 워커·부하테스트(GUI) 실행 스크립트 추가 (대화형)
기존 run_local_server.sh 스타일에 맞춘 대화형 .sh 두 개. - run_local_worker.sh: 동시성(WORKER_CONCURRENCY)·Chrome 프로필(LPS_PROFILE_DIR) 선택 후 워커 기동. 실행 중 워커 감지 시 교체 여부 확인. venv/config 자동 보장. - run_loadtest_gui.sh: Locust 웹 UI(:8089) 기동. 워커 실행 중이면 경고(크롤 비용), API 미기동 시 PROCESS_COUNT 받아 백그라운드 기동(종료 시 함께 정리). 부하생성기 --processes. - .gitignore(lps): .profiles/(Chrome 쿠키·cf_clearance 세션) 커밋 방지 - README·operations: 새 스크립트 안내로 갱신 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9c2432c818
commit
0e30354ba5
5
lps/.gitignore
vendored
Normal file
5
lps/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Chrome 프로필(쿠키·cf_clearance 세션) — 로컬 워커 영속 볼륨. 절대 커밋하지 않는다.
|
||||
.profiles/
|
||||
|
||||
# 로컬 워커 하트비트/부하 테스트 로그(기본은 /tmp 지만 상대경로로 뜰 때 대비)
|
||||
lps_worker_heartbeat
|
||||
@ -72,7 +72,10 @@ cp config/config.local.toml.example config/config.local.toml # 값 채우기
|
||||
./run_local_server.sh # → http://localhost:9600/docs
|
||||
|
||||
# 3) 워커 실행 (실제 검색 수행) — 별도 터미널
|
||||
python worker_main.py
|
||||
./run_local_worker.sh # 대화형: 동시성·프로필 선택 (또는 python worker_main.py)
|
||||
|
||||
# (선택) 부하 테스트 GUI — Locust 웹 UI(:8089)
|
||||
./run_loadtest_gui.sh # 브라우저에서 users/spawn 조절하며 RPS/지연 관측
|
||||
```
|
||||
|
||||
간단 테스트:
|
||||
@ -104,7 +107,9 @@ lps/
|
||||
├── web_main.py # API 서버 진입점 (요청 접수)
|
||||
├── worker_main.py # 워커 진입점 (검색+웜업+유휴정리+ops모니터)
|
||||
├── Dockerfile # API 이미지(lean) · Dockerfile.worker # 워커(Chromium+Xvfb)
|
||||
├── run_local_server.sh # 로컬 API 실행 스크립트
|
||||
├── run_local_server.sh # 로컬 API 실행 (대화형)
|
||||
├── run_local_worker.sh # 로컬 워커 실행 (대화형: 동시성·프로필)
|
||||
├── run_loadtest_gui.sh # 부하 테스트 Locust 웹 UI(:8089) 실행 (대화형)
|
||||
├── config/ # 설정(config.local.toml — 포트/DB/API키, 미커밋; 배포는 env 주입)
|
||||
├── common/ # 공통(enums, DB 세션, 모델, 로거)
|
||||
│ └── database/model/models.py # DB 테이블 정의
|
||||
|
||||
@ -35,20 +35,21 @@ psql -h 127.0.0.1 -U postgres -d lps_db -c "CREATE EXTENSION IF NOT EXISTS pgcry
|
||||
|
||||
**워커** (실제 검색 수행) — 별도 터미널
|
||||
```bash
|
||||
python worker_main.py
|
||||
# 로그를 실시간으로 보려면:
|
||||
PYTHONUNBUFFERED=1 python worker_main.py
|
||||
# 상품 여러 개를 동시에 검색(워커별 브라우저 세트 · 다른 IP):
|
||||
WORKER_CONCURRENCY=3 python worker_main.py # 권장 2~3(로컬). Chrome 최대 4×N개
|
||||
./run_local_worker.sh # 대화형: 동시성(WORKER_CONCURRENCY)·Chrome 프로필 선택
|
||||
# 또는 직접:
|
||||
PYTHONUNBUFFERED=1 python worker_main.py # 로그 실시간
|
||||
WORKER_CONCURRENCY=3 python worker_main.py # 동시성 2~3(로컬). Chrome 최대 4×N개
|
||||
```
|
||||
> 워커 실행 시 쿠팡 크롤링용 **Chrome 창이 뜹니다**(정상). 기동 로그에 `DECODO 프리플라이트 OK — egress IP ...`, `AI: ON/OFF`가 표시됩니다.
|
||||
> 동시성 N이면 상품 N개가 진짜 병렬 처리됩니다(각 워커가 자기 프로필·프록시 IP 사용).
|
||||
|
||||
**부하 테스트** (여러 상품 동시 검색 측정)
|
||||
**부하 테스트**
|
||||
```bash
|
||||
N=8 python loadtest.py # 상품 8개 제출→처리량·지연(p50/p95)·AI/DECODO/총비용 집계
|
||||
N=8 python loadtest.py # e2e: 상품 8개 제출→처리량·지연(p50/p95)·AI/DECODO/총비용 집계 (워커 필요)
|
||||
./run_loadtest_gui.sh # API 부하: Locust 웹 UI(:8089)에서 RPS/지연 실시간 관측 (워커 OFF)
|
||||
```
|
||||
> 워커 동시성만큼 병렬 처리됩니다(동시성 낮으면 큐에서 순차 대기 — 그게 부하 관측 포인트).
|
||||
> e2e(loadtest.py)는 워커 동시성만큼 병렬 처리됩니다(동시성 낮으면 큐에서 순차 대기 — 그게 부하 관측 포인트).
|
||||
> API 부하(GUI)는 enqueue/조회 경로만 측정하므로 **워커를 끄고** 실행합니다(실제 크롤 비용 회피).
|
||||
|
||||
## 2-1. 멀티코어 스케일 & 커넥션 풀 (자동)
|
||||
|
||||
|
||||
84
lps/run_loadtest_gui.sh
Executable file
84
lps/run_loadtest_gui.sh
Executable file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# LPS 부하 테스트 — Locust 웹 UI(GUI) 실행 (대화형).
|
||||
# 브라우저(:8089)에서 동시 유저 수·spawn rate 를 조절하며 실시간 그래프로 RPS/지연을 본다.
|
||||
# 대상 = API 서버(web_main, :9600). enqueue/조회 경로에 부하를 준다.
|
||||
#
|
||||
# ⚠️ 워커는 끄고 실행하라 — 부하 중 실제 크롤(프록시/AI 비용)이 발생한다.
|
||||
# 쌓인 부하 잡 정리: psql -h 127.0.0.1 -U postgres -d lps_db -c "TRUNCATE job;"
|
||||
#
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")" # lps/
|
||||
|
||||
VENV=".venv"
|
||||
PY="$VENV/bin/python"
|
||||
LOCUST="$VENV/bin/locust"
|
||||
HOST="http://localhost:9600"
|
||||
PORT=9600
|
||||
WEBUI_PORT=8089
|
||||
|
||||
# 1) venv + locust 보장
|
||||
if [[ ! -d "$VENV" ]]; then
|
||||
echo "[setup] venv 생성 + 의존성 설치..."
|
||||
python3 -m venv "$VENV"
|
||||
"$PY" -m pip install -q --upgrade pip
|
||||
"$PY" -m pip install -q -r requirements.txt
|
||||
fi
|
||||
if [[ ! -x "$LOCUST" ]]; then
|
||||
echo "[setup] locust 설치..."; "$PY" -m pip install -q locust
|
||||
fi
|
||||
|
||||
# 2) 워커가 떠 있으면 경고(부하 중 실제 크롤=비용)
|
||||
if pgrep -f worker_main.py >/dev/null 2>&1; then
|
||||
echo "[warn] 워커(worker_main.py)가 실행 중입니다 — 부하 중 실제 크롤 비용(프록시/AI)이 발생합니다."
|
||||
read -rp " 워커를 종료할까요? [Y/n]: " k
|
||||
[[ "${k:-Y}" =~ ^[Nn] ]] || { pkill -f worker_main.py || true; sleep 1; echo " 워커 종료"; }
|
||||
fi
|
||||
|
||||
# 3) API 기동 확인 — 없으면 띄울지 물어본다
|
||||
API_STARTED=""
|
||||
if curl -s "$HOST/healthz" >/dev/null 2>&1; then
|
||||
echo "[info] API 서버 감지됨 ($HOST)"
|
||||
else
|
||||
echo "[info] API 서버($HOST)가 응답하지 않습니다."
|
||||
read -rp " 이 스크립트가 API 를 띄울까요? [Y/n]: " s
|
||||
if [[ ! "${s:-Y}" =~ ^[Nn] ]]; then
|
||||
read -rp " 멀티코어 PROCESS_COUNT [1] (풀은 자동 산정): " PC; PC="${PC:-1}"
|
||||
echo " [run] web_main.py (PROCESS_COUNT=$PC) 백그라운드 기동..."
|
||||
PROCESS_COUNT="$PC" APP_ENV=local "$PY" web_main.py > /tmp/lps_api_loadtest.log 2>&1 &
|
||||
API_STARTED=$!
|
||||
for _ in $(seq 1 20); do curl -s "$HOST/healthz" >/dev/null 2>&1 && break; sleep 1; done
|
||||
if curl -s "$HOST/healthz" >/dev/null 2>&1; then
|
||||
echo " API 기동 완료 (로그: /tmp/lps_api_loadtest.log)"
|
||||
else
|
||||
echo " [error] API 기동 실패 — /tmp/lps_api_loadtest.log 확인"; kill "$API_STARTED" 2>/dev/null || true; exit 1
|
||||
fi
|
||||
else
|
||||
echo " [안내] 먼저 다른 터미널에서 ./run_local_server.sh 로 API 를 띄우세요."; exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) 부하 생성기 멀티프로세스(locust 자체가 병목되지 않게). 코어 많으면 늘린다.
|
||||
CORES=$("$PY" -c "import os;print(os.cpu_count())")
|
||||
read -rp "부하 생성기 프로세스 수 --processes [4] (CPU=$CORES): " LP; LP="${LP:-4}"
|
||||
|
||||
# API 를 이 스크립트가 띄웠으면 종료 시 함께 내린다
|
||||
cleanup() { [[ -n "$API_STARTED" ]] && { echo ""; echo "[info] 스크립트가 띄운 API 종료"; kill "$API_STARTED" 2>/dev/null || true; }; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " Locust 웹 UI: http://localhost:$WEBUI_PORT"
|
||||
echo " 대상 API : $HOST"
|
||||
echo " → 브라우저에서 users/spawn rate 입력 후 START"
|
||||
echo " 중단: Ctrl+C · 끝나면 쌓인 잡 정리:"
|
||||
echo " psql -h 127.0.0.1 -U postgres -d lps_db -c 'TRUNCATE job;'"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# API 를 이 스크립트가 띄웠으면 exec 하지 않는다(종료 시 trap 으로 API 를 함께 내리기 위해).
|
||||
if [[ -n "$API_STARTED" ]]; then
|
||||
"$LOCUST" -f loadtest/locustfile.py --host "$HOST" --web-port "$WEBUI_PORT" --processes "$LP"
|
||||
else
|
||||
exec "$LOCUST" -f loadtest/locustfile.py --host "$HOST" --web-port "$WEBUI_PORT" --processes "$LP"
|
||||
fi
|
||||
59
lps/run_local_worker.sh
Executable file
59
lps/run_local_worker.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# 로컬 LPS 워커 실행 (대화형). 실행하면 동시성 등을 골라 입력한다.
|
||||
# 최초 실행 시 venv 생성 + 의존성 설치까지 자동으로 한다.
|
||||
# 워커 = 실제 검색 수행(네이버/쿠팡/오픈마켓 크롤). 실행 시 쿠팡용 Chrome 창이 뜬다(정상).
|
||||
#
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")" # lps/
|
||||
|
||||
VENV=".venv"
|
||||
PY="$VENV/bin/python"
|
||||
|
||||
# 1) venv + 의존성 보장
|
||||
if [[ ! -d "$VENV" ]]; then
|
||||
echo "[setup] venv 생성 + 의존성 설치..."
|
||||
python3 -m venv "$VENV"
|
||||
"$PY" -m pip install -q --upgrade pip
|
||||
"$PY" -m pip install -q -r requirements.txt
|
||||
fi
|
||||
|
||||
# 2) config 보장
|
||||
if [[ ! -f config/config.local.toml ]]; then
|
||||
echo "[error] config/config.local.toml 이 없습니다. 아래로 생성 후 값을 채우세요:"
|
||||
echo " cp config/config.local.toml.example config/config.local.toml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3) 이미 워커가 떠 있으면 안내
|
||||
if pgrep -f worker_main.py >/dev/null 2>&1; then
|
||||
echo "[info] 이미 워커(worker_main.py)가 실행 중입니다."
|
||||
read -rp "기존 워커를 종료하고 새로 띄울까요? [y/N]: " kill_old
|
||||
if [[ "${kill_old:-N}" =~ ^[Yy] ]]; then
|
||||
pkill -f worker_main.py || true; sleep 1; echo "[info] 기존 워커 종료"
|
||||
else
|
||||
echo "취소합니다."; exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) 동시성 입력 (상품 동시 검색 수 · 워커별 브라우저 세트 · Chrome 최대 4×N개)
|
||||
echo "── 워커 설정 ──"
|
||||
read -rp "동시 검색 수 WORKER_CONCURRENCY [3] (로컬 권장 2~3): " CONC
|
||||
CONC="${CONC:-3}"
|
||||
|
||||
# 5) Chrome 프로필 영속 디렉터리 (재시작해도 cf_clearance 유지 → 재웜업 회피). 비우면 기본(/tmp)
|
||||
read -rp "Chrome 프로필 디렉터리 LPS_PROFILE_DIR [.profiles] (엔터=유지): " PROFILE
|
||||
PROFILE="${PROFILE:-.profiles}"
|
||||
mkdir -p "$PROFILE"
|
||||
|
||||
export APP_ENV=local
|
||||
export WORKER_CONCURRENCY="$CONC"
|
||||
export LPS_PROFILE_DIR="$PROFILE"
|
||||
export PYTHONUNBUFFERED=1 # 로그 실시간 출력
|
||||
|
||||
echo ""
|
||||
echo "[run] worker_main.py (동시성=$CONC · 프로필=$PROFILE)"
|
||||
echo " 기동 로그의 'DECODO 프리플라이트 OK — egress IP ...' / 'AI: ON/OFF' 확인"
|
||||
echo " 중단: Ctrl+C"
|
||||
echo ""
|
||||
exec "$PY" worker_main.py
|
||||
Loading…
Reference in New Issue
Block a user