**낡은 테스트**: test_handler_skips_record_on_negative_cache_hit 는 '캐시 히트면 이력을 남기지 않는다'를 검증했는데, 그 동작은 실측 버그였다 — 잡은 DONE 인데 price_history 에 새 행이 없어 이를 폴링하는 소비자(negodata 최저가 모달)가 결과를 영영 못 받고 로딩만 돌았다. 코드는 이미 '캐시 히트도 이 잡의 결과이므로 기록한다'로 고쳐져 있었고 테스트만 남아 있었다. → 현재 계약(not_found 스냅샷 1건 기록, 가격은 null)을 검증하도록 다시 씀. 전체 220 passed·0 failed. **오픈API 어댑터 제거**: shop.json 이 2026-07-31 종료돼 404 SE05 만 반환하고, 파이프라인은 naver_shop(크롤)로 옮겨 갔다. 되살릴 수 없는 코드를 남겨두면 다음 사람이 "키를 넣으면 되나" 하고 시간을 쓴다. - services/search/naver/ (adapter·transform) 삭제 - NaverConfig 모델·로더·설정 섹션 3개 파일에서 제거(죽은 키) - test_naver_transform 삭제, test_alerts 는 NaverAdapter 대신 스텁 사용 (검증 대상인 recent_stats/_note_result 는 베이스 SearchAdapter 계약이라 무관) **문서 정합화**: architecture(네이버 안티봇=WTM, 통과 3조건) · api(배송비가 이제 채워짐, 가격은 즉시판매가·쿠폰가 제외) · operations(kr_host·naver_ip_request_budget) · README 트리. source 이름 "naver" 는 그대로다 — price_history·by_mall·프론트 계약은 구현 교체와 무관하다.
81 lines
3.0 KiB
Plaintext
81 lines
3.0 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"
|
|
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 에 몰리면 이 값과 무관한 문제다
|
|
port_cooldown_sec = 0 # 0=자동 max(sticky, 30분)
|