o2o-negosium-original/lps/config/config.prod.toml.example
민헌 08afc7d4ad fix(lps): sticky 만료 회전이 저트래픽에서 안 먹던 문제 + prod 템플릿에 kr_host 누락
prod 영향 점검 중 발견. 직전 커밋(377389f)의 sticky 만료 회전은 **브라우저가 열려 있을 때만**
동작했다 — `_ctx is None` 이면 만료 검사를 건너뛰었다. 그런데 negodata 연동은 수동 트리거
전용이라 검색이 드문드문 들어오고, 그때는 유휴 정리(120s)로 브라우저가 닫힌 채 매 검색이
그 경로로 들어온다. 즉 **실사용 패턴에서만 안 먹는** 반쪽 수정이었다(377389f 커밋 메시지·README
의 '고쳤다'는 서술이 부정확했다).

실측(합성 게이트웨이·10포트, sticky 매번 경과):
  연속 검색            IP 6개 순환      
  저트래픽 · 예산 3     IP 2개          예산이 대신 회전시켜 가려져 있었음
  저트래픽 · 예산 0     **IP 1개 고정**    ([DecodoConfig] 주석의 '0=시간창 회전만'이 거짓)

원인은 시계가 둘이었던 것이다. sticky 만료를 브라우저 기동 시각(_launched_at)으로 쟀는데,
브라우저는 닫혔다 열릴 때마다 시계가 되감긴다. IP 를 쥔 시간과 어긋나는 이 구조가 F1(예산
미발화)과 F4(회전 안 됨)의 공통 원인이었다.
→ 시계를 _session_started_at 하나로 통일하고 _launched_at 을 제거했다. 만료 판정은 브라우저가
   닫혀 있어도 수행한다. 세 시나리오 모두 정상 회전 확인.

**prod 템플릿 kr_host 누락**(기존 문제, 이번 변경과 무관):
config.prod.toml.example 에 kr_host 가 없어 그대로 복사하면 네이버가 국가 무지정 게이트웨이로
떨어진다. 해외 residential IP 는 '비정상적인 접근'(2.6KB) 하드차단이고 회전으로 회복 불가라
네이버 결과가 통째로 0건이 된다. kr_host + naver_ip_request_budget 을 경고 주석과 함께 추가.

테스트 2건 추가(저트래픽 sticky 만료 회전 / IP 를 쥔 시간이 수명 내면 회전 안 함), 전체 253 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 11:50:08 +09:00

86 lines
3.6 KiB
Plaintext

# 배포서버용. 복사해서 사용: cp config.prod.toml.example config.prod.toml
# 실제 config.prod.toml 은 시크릿 포함이라 커밋하지 않는다.
#
# ⚠️ 로드 경로 주의 — docker-compose.prod.yml 이 이 파일을 config.local.toml 자리에 마운트한다:
# ./lps/config/config.prod.toml : /app/config/config.local.toml : ro
# 컨테이너는 APP_ENV=local 로 뜨므로 코드가 읽는 파일명은 config.local.toml 이지만
# 내용은 이 파일이다. 파일명만 보고 "prod 설정이 안 읽힌다"고 오해하기 쉽다.
#
# 로컬(config.local.toml.example)과 다른 항목만 ★ 로 표시했다.
[WebServerConfig]
server_name = "LpsServer"
port = 9600
process_count = 1
is_ssl = false # 리버스프록시 뒤 — TLS 는 프록시가 종단
is_test = false # ★ prod
cors_origins = [] # ★ 브라우저가 직접 호출하지 않으면 비움
api_keys = ["<API_KEY>"] # ★ prod 는 반드시 채운다(비면 무인증 개방). openssl rand -hex 32
[LogConfig]
print_console = true
log_level = "info" # ★ prod
[MainDBConfig]
db_type = "postgresql"
name = "lps_db"
write_host = "host.docker.internal" # ★ 컨테이너 → 호스트 DB (compose extra_hosts)
write_port = 5432
write_id = "<DB_USER>"
write_pw = "<DB_PASSWORD>"
read_host = "host.docker.internal" # ★
read_port = 5432
read_id = "<DB_USER>"
read_pw = "<DB_PASSWORD>"
show_log = false
pool_size = 10
max_overflow = 20
connection_budget = 40
sslmode = "" # 관리형 DB 면 "require"
[WorkerConfig]
concurrency = 1 # ★ 동시성을 올리면 차단 시 IP 소모도 비례해 늘어난다
fallbacks = []
profile_dir = "/profiles" # ★ 필수 — compose 가 lps-profiles 볼륨을 여기에 마운트한다.
# ".profiles" 로 두면 컨테이너 레이어에 쌓여 재생성마다 쿠키가 날아간다.
job_deadline_sec = 300
shutdown_grace_sec = 60
chrome_channel = "chrome"
chrome_executable = "/opt/google/chrome/chrome" # ★ 컨테이너 필수(설정 시 --no-sandbox 가 함께 붙음)
heartbeat_file = "/tmp/lps_worker_heartbeat"
[AlertConfig]
webhook = "" # ★ Slack 호환 웹훅. 비우면 로그로만 알림
cooldown_min = 30
dead_1h = 20
blocks_1h = 80
queue_lag_sec = 300
pool_pct = 90
source_fail_30m = 5
deadline_1h = 5
cost_1h_usd = 1.0
ports_low_pct = 30
block_sessions_6h = 1
# ── 시크릿 ──
[OpenAIConfig]
api_key = "<OPENAI_API_KEY>" # 소진되면 크롤이 성공해도 AI 판정 실패로 잡이 DEAD 된다
model = "gpt-4o-mini"
[DecodoConfig]
host = "gate.decodo.com"
# ⚠️ 네이버는 **한국 IP 가 필수**다. 이 줄이 비면 네이버가 host(국가 무지정)로 떨어지고,
# 해외 residential IP 로는 매 검색이 하드차단('비정상적인 접근', 2.6KB)된다 — IP 를 아무리
# 회전해도 회복되지 않는 구조적 차단이라 네이버 결과가 통째로 0건이 된다(fatal_block 알림).
kr_host = "kr.decodo.com" # 한국 타깃 게이트웨이(네이버용). 비우면 host 사용 = 네이버 불가
username = "<DECODO_USERNAME>"
password = "<DECODO_PASSWORD>"
port_start = 10001
port_end = 10100
session_minutes = 10
cost_per_gb = 3.0
ip_request_budget = 3 # 쿠팡 IP당 요청 예산. 차단이 ip_req#1 에 몰리면 이 값과 무관한 문제다
naver_ip_request_budget = 10 # 네이버는 IP 당 12회까지 무차단 실측 → 여유 둔 10
port_cooldown_sec = 0 # 0=자동 max(sticky, 30분)