P4/P5 반영: /readyz·/v1/lps/ops·워커 하트비트·임계 알림 env, LPS_LIVE 라이브 스모크, docker compose 배포(헤드풀 Chromium+Xvfb·시크릿 env·프로필 볼륨·HEALTHCHECK). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
162 lines
8.6 KiB
Markdown
162 lines
8.6 KiB
Markdown
# 운영 가이드 — 실행 · 로그 · DB · 문제 해결
|
||
|
||
[← README로](../README.md)
|
||
|
||
## 1. 사전 준비
|
||
|
||
**필요한 것**: Python 3.12+ (로컬은 3.14), PostgreSQL, Google Chrome(쿠팡 크롤링용)
|
||
|
||
**설정 파일** (`config/config.local.toml`, git 미커밋)
|
||
```bash
|
||
cp config/config.local.toml.example config/config.local.toml
|
||
```
|
||
채워야 할 값:
|
||
| 섹션 | 값 |
|
||
|------|-----|
|
||
| `[MainDBConfig]` | DB 접속(host/port/id/pw, name=lps_db) |
|
||
| `[NaverConfig].keys` | 네이버 쇼핑 API 키(id/secret). 여러 개면 자동 로테이션 |
|
||
| `[OpenAIConfig]` | `api_key` (AI 판정·검색어 생성용) |
|
||
| `[DecodoConfig]` | 프록시 정보(비워두면 프록시 미사용) |
|
||
|
||
> **한 파일에 설정+시크릿 통합** 관리. 배포 시엔 이 파일을 마운트하거나, 환경별로 바뀌는 값(DB_HOST 등)만 환경변수로 덮어씁니다.
|
||
|
||
**DB 준비**: `lps_db` 생성 후 최초 실행 시 테이블 자동 생성.
|
||
```bash
|
||
createdb -h 127.0.0.1 -U postgres lps_db
|
||
psql -h 127.0.0.1 -U postgres -d lps_db -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
|
||
```
|
||
|
||
## 2. 실행
|
||
|
||
**API 서버** (요청 접수)
|
||
```bash
|
||
./run_local_server.sh # → http://localhost:9600/docs
|
||
```
|
||
|
||
**워커** (실제 검색 수행) — 별도 터미널
|
||
```bash
|
||
python worker_main.py
|
||
# 로그를 실시간으로 보려면:
|
||
PYTHONUNBUFFERED=1 python worker_main.py
|
||
# 상품 여러 개를 동시에 검색(워커별 브라우저 세트 · 다른 IP):
|
||
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/총비용 집계
|
||
```
|
||
> 워커 동시성만큼 병렬 처리됩니다(동시성 낮으면 큐에서 순차 대기 — 그게 부하 관측 포인트).
|
||
|
||
## 3. 로그 보는 법 (워커 터미널)
|
||
|
||
| 로그 | 의미 |
|
||
|------|------|
|
||
| `DECODO 프리플라이트 OK — egress IP ...` | 시작 시 살아있는 프록시 포트 선점 성공(egress IP 표시) |
|
||
| `[warmup:gmarket] 챌린지 통과·쿠키 확보` | 시작 웜업 — 챌린지 미리 풀어 쿠키 선점(실 작업 웜) |
|
||
| `[naver] query='...' → N건` | 네이버 검색 결과 수 |
|
||
| `[coupang] query='...' → N건 (ip_req#K)` | 쿠팡 결과 수 / 이 IP로 K번째 요청 |
|
||
| `[gmarket/auction/st11] query='...' → N건` | 오픈마켓 폴백 크롤 결과 수 |
|
||
| `[ai] 판정 N건 중 매칭 M건` | AI 같은상품 선별 결과 |
|
||
| `[coupang][BOT-DETECTED] ... marker='...'` | 봇 감지(마커별) → IP 회전 |
|
||
| `[gmarket] IP 회전 — 프록시 전송오류/봇 감지` | 프록시 죽음(407/터널) 또는 차단 → 새 IP |
|
||
| `[fallback:gmarket] 데드라인 15s 초과 → 스킵` | 폴백 크롤이 시간 상한 초과 → 그 몰만 스킵 |
|
||
| `[coupang] 유휴 120s 초과 → 브라우저 정리` | 유휴 브라우저 닫아 메모리 회수(다음 검색 때 재기동) |
|
||
| `[worker-0] done <id>` / `fail ... → DEAD` | 작업 완료 / 실패 |
|
||
|
||
> 디버그 로그가 안 보이면 `config.local.toml`의 `[LogConfig] log_level = "debug"` 확인.
|
||
|
||
## 4. DB 조회 (유용한 쿼리)
|
||
|
||
```bash
|
||
psql -h 127.0.0.1 -U postgres -d lps_db
|
||
```
|
||
```sql
|
||
-- 큐 상태 요약 (1=대기 2=처리중 3=완료 4=실패)
|
||
SELECT status, count(*) FROM job GROUP BY status;
|
||
|
||
-- 최근 작업 결과
|
||
SELECT job_id, status, result->>'outcome' AS outcome,
|
||
result->'lowest'->>'price' AS lowest, result->'sources' AS sources
|
||
FROM job ORDER BY created_at DESC LIMIT 5;
|
||
|
||
-- 특정 상품의 최저가 이력(그래프 원본) + 몰별 스냅샷
|
||
SELECT triggered_at, naver_lowest, coupang_lowest, final_lowest, final_source, by_mall
|
||
FROM price_history WHERE product_code='T1' ORDER BY triggered_at;
|
||
|
||
-- 검색 원가(최근 완료 작업의 metrics)
|
||
SELECT job_id,
|
||
result->'metrics'->'cost'->>'total_usd' AS 총비용,
|
||
result->'metrics'->'cost'->>'proxy_usd' AS DECODO,
|
||
result->'metrics'->'crawl'->>'proxy_bytes' AS 전송바이트,
|
||
result->'metrics'->>'duration_ms' AS 소요ms
|
||
FROM job WHERE status=3 ORDER BY updated_at DESC LIMIT 5;
|
||
|
||
-- 봇 감지 패턴 (IP당 평균 몇 요청 만에 감지?)
|
||
SELECT avg(ip_request_no), count(*) FROM bot_detection;
|
||
|
||
-- 네거티브 캐시(없음으로 기록된 상품)
|
||
SELECT key, until, reason FROM search_negative ORDER BY created_at DESC;
|
||
```
|
||
|
||
## 4-1. 관측·알림 (모니터링)
|
||
|
||
| 엔드포인트/신호 | 용도 |
|
||
|------|------|
|
||
| `GET /healthz` | liveness — 프로세스 살아있는지(DB 무관) |
|
||
| `GET /readyz` | readiness — DB 도달성까지 확인(실패 503). LB/오케스트레이터용 |
|
||
| `GET /v1/lps/ops` | 운영 스냅샷: 큐 카운트 + `oldest_pending_sec`(큐 지연) + `dead_1h` + `stuck_running` + `blocks_1h`(최근 차단). 외부 모니터가 스크랩·알림 |
|
||
| 워커 하트비트 | `/tmp/lps_worker_heartbeat`(mtime) — 컨테이너 HEALTHCHECK 가 신선도<120s 로 행/좀비 워커 감지 |
|
||
|
||
**임계 알림**(워커 ops-monitor): 초과 시 WARN 로그 + (env 있으면) Slack 호환 웹훅.
|
||
```
|
||
LPS_ALERT_WEBHOOK=https://hooks.slack.com/... # 있으면 알림 전송
|
||
LPS_ALERT_DEAD_1H=20 LPS_ALERT_BLOCKS_1H=80 LPS_ALERT_QUEUE_LAG_SEC=300
|
||
```
|
||
|
||
## 5. 테스트
|
||
|
||
```bash
|
||
python -m pytest # 단위·통합(96) — 브라우저/네트워크 불필요
|
||
LPS_LIVE=1 python -m pytest tests/test_browser_base.py::test_live_smoke # 라이브 스모크(셀렉터·안티봇 드리프트 감지)
|
||
```
|
||
> ⚠️ **워커가 실행 중이면 테스트가 깨집니다** — 워커가 같은 `lps_db`의 테스트 작업을 가로채기 때문. 테스트 전 워커를 멈추세요:
|
||
> ```bash
|
||
> pkill -f worker_main.py
|
||
> ```
|
||
> 라이브 스모크는 IP 의존·느려서 기본 skip. 배포 후 셀렉터가 깨졌는지 수동/야간 점검용.
|
||
|
||
## 6. 문제 해결
|
||
|
||
| 증상 | 원인 / 해결 |
|
||
|------|------------|
|
||
| 포트 9600 사용 중 | `lsof -ti:9600 \| xargs kill` 후 재실행 |
|
||
| 백그라운드 실행 시 로그 안 보임 | `print` 버퍼링 → `PYTHONUNBUFFERED=1` 붙여 실행 |
|
||
| `프리플라이트 실패`/모든 크롤 실패 | DECODO 프록시 문제 — **대시보드에서 잔여 트래픽·플랜·자격증명** 확인(407=인증거부). 게이트 다운이면 네이버(직접)만 동작 |
|
||
| G마켓 결과 계속 0건 | Cloudflare Turnstile 미통과(나쁜 IP는 인터랙티브 체크박스) — 웜업 IP회전 재시도로 완화. 지연 부담이면 폴백 데드라인이 스킵 |
|
||
| 쿠팡 `blocked=True`(Access Denied 등) | Akamai 차단 → 자동 IP 회전(감지 이력 `bot_detection`). 반복되면 프록시 IP 풀 확대 |
|
||
| Chrome이 계속 쌓임 | 유휴 정리(120s)가 닫음. 스파이크/이전 워커 잔여는 `pkill -f "user-data-dir=/tmp/lps_"` |
|
||
| AI 매칭이 0건 자주 발생 | 검색어 모호/스펙 불일치 → `product_name`/`specification`을 더 정확히 |
|
||
| 검색이 너무 느림/비쌈 | `result.metrics`로 소스별 시간·DECODO 바이트 확인. 대역폭이 대부분(오픈마켓 크롤) |
|
||
| `result.desc = LPS_JOB_NOT_FOUND` | 존재하지 않거나 잘못된 job_id |
|
||
|
||
## 7. Docker 배포
|
||
|
||
```bash
|
||
# 루트에서 (DB 는 외부 PostgreSQL, host.docker.internal 로 연결)
|
||
docker compose build lps-api lps-worker
|
||
docker compose up -d lps-api lps-worker
|
||
docker logs -f lps-worker # 웜업·검색 로그
|
||
docker ps # lps-worker "(healthy)" 확인
|
||
```
|
||
- **워커 = 헤드풀 Chromium + Xvfb**(`Dockerfile.worker`): **headless 는 Akamai·Cloudflare Turnstile 에 탐지됨**(실측). Xvfb 가상 디스플레이로 headful 실행.
|
||
- **API = lean**(`Dockerfile`, 브라우저 불필요).
|
||
- **시크릿은 이미지에 안 굽고 env 주입**: `OPENAI_API_KEY`·`DECODO_*`·`NAVER_KEYS` (compose 주석 참고). 로컬은 config.local.toml.
|
||
- **Chrome 프로필 영속 볼륨**(`lps-profiles:/profiles`, `LPS_PROFILE_DIR`): 재시작해도 cf_clearance 유지 → 재웜업 회피.
|
||
- **워커 헬스**: HEALTHCHECK(하트비트<120s)로 행 워커 감지. k8s 는 liveness probe 로 자동 재시작 연결.
|
||
|
||
**남은 배포 과제**: API 인증·레이트리밋(비용 남용 방지), 다중 레플리카 시 분산 레이트리밋/프록시 IP 조정.
|
||
**비용**: 대역폭이 원가의 대부분(오픈마켓 크롤) — 같은 상품 재크롤을 줄이는 **TTL 캐시**가 다음 절감 후보.
|