diff --git a/lps/docs/architecture.md b/lps/docs/architecture.md index e61e6c5..534ff1e 100644 --- a/lps/docs/architecture.md +++ b/lps/docs/architecture.md @@ -107,6 +107,57 @@ - **예산 가이드**: 공유 PG=40(API+worker+타 서비스 공존, 안정 우선) / 전용 PG(`max_connections≈100`)=90(처리량 우선). env `DB_CONNECTION_BUDGET`. 더 큰 처리량은 예산↑ + PG `max_connections`↑ 또는 pgbouncer. - 상세·벤치 결과: [`../loadtest/README.md`](../loadtest/README.md). +### 6-2. 왜 브라우저는 워커당 1세트인가 (더 띄우면 안 되나?) + +Chrome 프로세스 자체는 얼마든지 더 띄울 수 있다. 그런데도 워커:브라우저를 1:1로 두는 이유 — +정확히는 **"더 띄워봐야 이득이 0이고, 덜 띄우면(공유하면) 손해"**라서 1:1이 낭비도 병목도 없는 균형점이다. + +- **희소 자원은 브라우저가 아니라 "신원(identity)"**. 쿠팡용 브라우저 1개 = Chrome 프로필(Akamai 쿠키, + cf_clearance) + DECODO sticky IP(포트) 묶음이다. Akamai 는 쿠키와 IP 를 묶어 보므로(불일치 시 재챌린지) + 브라우저 추가 = 웜업 챌린지 1회 추가 + 프록시 포트 1개 소모 + 봇 감지 노출면 확대다. Chrome 은 공짜여도 + **쓸 수 있는 신원은 공짜가 아니다.** +- **검색 1건은 브라우저 여러 개로 못 쪼갠다.** 검색은 "페이지 열고 → 렌더 대기 → 파싱"의 순차 작업. + 병렬화 단위는 검색이 아니라 **잡(=상품)**이고, 잡의 동시 처리 주체가 워커다. 워커는 소스당 검색을 + 한 번에 하나만 하므로 소스당 브라우저 1개면 항상 꽉 채워 쓴다. +- **비율이 어긋나면**: 브라우저 > 워커 → 남는 브라우저는 놀면서 메모리·포트·웜업 비용만 차지(처리량 +0). + 브라우저 < 워커(공유) → 어댑터가 브라우저 상태(페이지·봇감지 회전 상태머신) 때문에 검색을 락으로 + 직렬화하므로 사실상 순차가 된다(2026-07-10 스톨 사건이 정확히 이 모습 — 웜업이 락을 쥐자 그 워커의 + 검색 전체가 정지). +- **처리량 상한도 브라우저 수가 아니다.** 신원 하나당 요청 간격을 일부러 2~8s 랜덤으로 벌린다(등간격 + 기계 요청 = 탐지 신호). 즉 신원당 처리량은 설계상 캡 — 처리량을 올리는 유일한 방법은 신원(=워커)을 + 늘리는 것이다. + +### 6-3. 워커 수 상한을 서버에서 파악하는 법 + +`워커 상한 = min( RAM캡, 포트캡, 실측 정체점 )` — 계산 가능한 하드캡 2개로 범위를 좁히고, 그 안에서 계단식 실측. + +**하드캡(계산)** + +- **RAM캡** = `(전체 RAM − OS/PG/API 여유분) × 0.7 ÷ 세트당 RSS`. + 세트당 RSS 는 워커 N개로 부하를 건 상태에서 chrome 프로세스 그룹 RSS 합 ÷ N (모니터 :9700 의 + 프로세스 그룹, 정밀하게는 `ps` RSS 합산). headful Chrome 세트는 리소스 차단을 해도 세트당 + 수백 MB~1GB — 16GB 서버면 대략 8~12세트에서 걸린다. 폴백을 켜면 워커당 브라우저 최대 4개로 배수 증가. +- **포트캡** ≈ `DECODO 포트 수 ÷ 3`. 워커마다 다른 sticky 포트가 필요하고 봇 감지 시 다음 포트로 + 회전하므로 회전 여유분이 필수 — 워커 수가 포트 수에 근접하면 회전 후 옆 워커가 쓰던 IP 를 받는 + 충돌이 생긴다. `config` 의 `port_start~port_end`에서 바로 계산. + +**소프트캡(실측 — 보통 이게 진짜 상한)** + +`WORKER_CONCURRENCY` 3→6→9… 계단으로 올리며 매번 같은 부하(`N=30 loadtest.py` 등)를 걸고, +아래 신호 중 **먼저 오는 것**이 그 서버의 상한: + +1. **처리량 정체** — 워커 2배인데 상품/분이 2배가 안 됨(리포트 숫자로 비교). +2. **봇 감지율 급증** — `blocks_1h`가 워커 수보다 가파르게 상승. 프록시 재시도 비용+차단 리스크라 + CPU 보다 먼저 멈춰야 하는 신호. +3. **메모리 압박** — macOS `memory_pressure` / Linux swap 시작. Chrome 은 부족하면 느려지는 게 아니라 크래시. +4. **워커 파이썬 프로세스의 단일 코어 포화** — 워커 N개는 **파이썬 프로세스 1개 안의 asyncio 태스크**라 + 파이썬 쪽 일(파싱·AI 응답 처리·DB)은 코어 1개를 공유한다. 모니터에서 worker 프로세스가 코어 1개 + 기준 100%에 붙으면 그 이상은 무의미. (Chrome 들은 별도 프로세스라 나머지 코어로 퍼진다.) +5. **p95 지연 상승** — 상품당 지연이 워커 늘리기 전보다 나빠지면 경합(락·프록시·DB) 시작. + +한 서버의 실측 상한을 넘는 처리량이 필요하면 워커 컨테이너를 **수평 확장**한다(§1 — 큐 기반이라 +워커만 늘리면 됨. `Dockerfile.worker` + compose). + ## 7. 최저가 이력 (그래프) - **트리거 기반**: 자동 배치 없이 **실제 조회된 상품만** 그 시점에 기록 → 트래픽·비용 절약.