o2o-negosium-original/lps
민헌 ba9d34fd31 [fix] lps: 네거티브 캐시 히트 시 이력 누락 수정 + force 재검색 지원
1) 캐시 히트가 price_history 를 남기지 않던 문제
   not_found 는 24시간 네거티브 캐시에 들어가는데, 캐시에 걸린 조기 반환 경로만
   _record_history 를 호출하지 않았다(다른 모든 경로는 호출).
   그 결과 잡은 완료인데 price_history 에 새 행이 없어, 이를 폴링하는 소비자
   (negodata 최저가 모달)가 결과를 영영 못 받고 로딩만 돌았다.
   → 캐시 히트도 '이 잡의 결과'이므로 이력을 남긴다.

2) force 플래그
   negodata 는 이제 수동 트리거 전용인데 is_negative() 가 job_type 을 보지 않아
   사람이 직접 누른 재검색까지 캐시가 가로막았다. 게다가 캐시 키가 product_code 라
   상품명·모델을 고쳐 재시도해도 동일하게 막힌다.
   → SearchItem.force=true 면 NegativeCache.drop() 으로 기록을 지우고 실제 검색.
   기본 요청은 캐시를 그대로 써서 비용 절감 효과는 유지.

실측: 캐시에 막혀 not_found 만 반복하던 상품이 force 재검색에서 2라운드 만에 found(8,500원).
⚠️ protocol.py 변경은 lps-api 와 lps-worker 를 함께 재빌드해야 반영된다
   (API 만 옛 스키마면 pydantic 이 force 를 조용히 버린다 — 실측으로 확인).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:55:23 +09:00
..
common refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거 2026-07-13 21:11:31 +09:00
config [fix] lps: 워커 이미지 chromium→google-chrome-stable 교체 (쿠팡 Akamai 지문 차단 회피) 2026-07-24 17:12:20 +09:00
crud [fix] lps: 네거티브 캐시 히트 시 이력 누락 수정 + force 재검색 지원 2026-07-28 11:55:23 +09:00
docs feat(lps): docker-compose 에 lps-admin 추가 + 환경설정 config.local.toml 단일화 2026-07-16 11:54:32 +09:00
loadtest refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거 2026-07-13 21:11:31 +09:00
migrations feat(lps): IP 선제 로테이션 — 요청 예산·포트 쿨다운·ip_session 관측 2026-07-13 16:35:44 +09:00
router [fix] lps: 네거티브 캐시 히트 시 이력 누락 수정 + force 재검색 지원 2026-07-28 11:55:23 +09:00
services feat(lps): 관리자 FE 용 API 6종 — 잡 목록/재큐·상품 목록·IP세션/차단/비용 통계 2026-07-13 21:42:38 +09:00
tests feat(lps): 관리자 FE 용 API 6종 — 잡 목록/재큐·상품 목록·IP세션/차단/비용 통계 2026-07-13 21:42:38 +09:00
worker [fix] lps: 네거티브 캐시 히트 시 이력 누락 수정 + force 재검색 지원 2026-07-28 11:55:23 +09:00
.dockerignore fix(lps): Docker 이미지 무시크릿화 + 배포 견고화 — 리뷰 발견사항 일괄 적용 2026-07-10 11:53:47 +09:00
.gitignore feat(lps): 로컬 워커·부하테스트(GUI) 실행 스크립트 추가 (대화형) 2026-07-10 08:33:11 +09:00
conftest.py feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가 2026-07-08 15:18:27 +09:00
Dockerfile fix(lps): Docker 이미지 무시크릿화 + 배포 견고화 — 리뷰 발견사항 일괄 적용 2026-07-10 11:53:47 +09:00
Dockerfile.worker [fix] lps: 워커 이미지 chromium→google-chrome-stable 교체 (쿠팡 Akamai 지문 차단 회피) 2026-07-24 17:12:20 +09:00
loadtest.py test(lps): 부하테스트 상품셋 100종 카탈로그 분리 — 실제 입력 분포 재현 2026-07-10 10:50:28 +09:00
pytest.ini feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가 2026-07-08 15:18:27 +09:00
README.md feat(lps): docker-compose 에 lps-admin 추가 + 환경설정 config.local.toml 단일화 2026-07-16 11:54:32 +09:00
requirements-api.txt fix(lps): Docker 이미지 무시크릿화 + 배포 견고화 — 리뷰 발견사항 일괄 적용 2026-07-10 11:53:47 +09:00
requirements.txt feat(lps): 실시간 관측 대시보드 추가 — 큐 추이·코어별 CPU·프로세스 그룹 사용률 2026-07-10 09:50:14 +09:00
run_docker.sh feat(lps): docker-compose 에 lps-admin 추가 + 환경설정 config.local.toml 단일화 2026-07-16 11:54:32 +09:00
run_loadtest_gui.sh feat(lps): 로컬 워커·부하테스트(GUI) 실행 스크립트 추가 (대화형) 2026-07-10 08:33:11 +09:00
run_local_server.sh refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거 2026-07-13 21:11:31 +09:00
run_local_worker.sh refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거 2026-07-13 21:11:31 +09:00
run_monitor.sh feat(lps): 실시간 관측 대시보드 추가 — 큐 추이·코어별 CPU·프로세스 그룹 사용률 2026-07-10 09:50:14 +09:00
web_main.py feat(lps): process_count 기반 커넥션 풀 자동 산정 — 멀티코어 풀 오버서브스크립션 방지 2026-07-09 23:59:05 +09:00
worker_main.py refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거 2026-07-13 21:11:31 +09:00

LPS — 인터넷 최저가 검색 솔루션

상품 정보를 넣으면 네이버·쿠팡을 뒤져 "같은 상품"의 최저가를 찾아 돌려주고, 그 가격을 시간에 따라 기록해 그래프로 볼 수 있는 시스템입니다.


🧭 이게 뭔가요? (비개발자용 3줄 요약)

  1. "맥심 커피 (1박스, 160개입)" 같은 상품 정보를 보내면,
  2. 시스템이 네이버·쿠팡을 실제로 검색하고, AI가 "진짜 같은 상품"만 골라 최저가를 알려줍니다. (빨대·커버 같은 엉뚱한 액세서리는 걸러냅니다)
  3. 같은 상품을 여러 번 조회하면 가격 변화가 쌓여서, 네이버/쿠팡/최종 최저가를 그래프로 볼 수 있습니다.

왜 유용한가? 사람이 일일이 검색·비교하지 않아도, 필요한 상품만(조회할 때만) 자동으로 최저가를 찾고 가격 추이를 남깁니다.


⚙️ 어떻게 동작하나요? (워크플로우)

 [1] 검색 요청           [2] 대기줄(큐)         [3] 일꾼(워커)가 처리
  상품 정보 전송   ─────▶  순서대로 쌓임   ─────▶  네이버 + 쿠팡 동시 검색
 (POST /search)          (즉시 접수번호 반환)         │
                                                     ▼
                                        [4] 걸러내기 + AI 판정
                                        가격 이상치 제거 → "같은 상품"만 선별
                                                     │
                                                     ▼
                                        [4-1] 오픈마켓 폴백 (옵션 · 기본 꺼짐)
                                        네이버가 못 덮은 몰(G마켓·옥션·11번가)만 크롤
                                                     │
                                                     ▼
                                        [5] 최저가 확정 + 기록
                                        네이버/쿠팡/최종 + 몰별 최저가 저장 → 이력 적재
                                                     │
                                                     ▼
                                        [6] 완료(결과 + 검색 원가 조회 가능)

핵심 포인트

  • 즉시 응답 + 나중 처리: 요청하면 바로 "접수번호(job_id)"를 주고, 실제 검색은 뒤에서 진행됩니다. (검색은 몇 초~수십 초 걸림)
  • 못 찾으면 검색어를 바꿔 재시도: "맥심 커피"로 안 나오면 "맥심 모카골드 커피믹스"처럼 AI가 검색어를 다듬어 다시 시도하고, 그래도 없으면 "없음"으로 정리합니다. (무한 재시도 안 함)
  • 차단 대응: IP당 요청 예산(기본 3회)에 닿으면 차단당하기 전에 IP를 선제 교체하고(평판 보존 — 그 IP는 로테이션 복귀 시 재사용), 그래도 감지되면 그 포트를 쿨다운 격리 후 다른 IP로 재시도합니다. 시작 시 챌린지를 미리 풀어(웜업) 실 작업을 빠르게 합니다.
  • 원가 투명: 검색 1건이 쓴 AI 비용·프록시 대역폭·시간을 함께 기록합니다.

주요 기능

기능 설명
멀티 소스 검색 네이버 쇼핑 API + 쿠팡(Akamai 우회) 동시 검색·병합
오픈마켓 폴백 크롤 네이버가 못 덮은 몰만 G마켓·옥션(Cloudflare Turnstile 우회)·11번가 크롤 → 몰별 가격. 기본 비활성([WorkerConfig].fallbacks, 배경)
AI 같은 상품 판정 "진짜 그 상품"만 선별 (액세서리·다른 규격 제외)
검색어 자동 정제 0건이면 정밀/광역 검색어로 재시도
최저가 이력 그래프 조회 시점마다 네이버/쿠팡/최종 + 몰별(by_mall) 최저가를 시계열로 기록
검색 원가 계측 검색 1건의 AI 토큰·비용 + DECODO 대역폭(실측 CDP) + 시간을 집계
다중 상품 병렬 워커별 브라우저 세트로 여러 상품 동시 검색(WORKER_CONCURRENCY)
안정적 큐 처리 작업 유실 없이 순서대로, 실패 시 자동 재시도
프록시 IP 선제 회전 요청 예산(기본 3회) 도달 시 차단 전 선제 교체 + 불탄 포트 쿨다운 + 봇 감지·전송오류 즉시 순환 + 시작 웜업(DECODO). 예산 튜닝용 ip_session 관측 로그
임계 알림 큐·차단·DB풀·소스별 장기실패·비용 등 10룰 — 쿨다운(스팸 방지)·해소 알림, Slack 웹훅(룰 표)
API guard [WebServerConfig].api_keys 설정 시 /v1 전체 X-API-Key 검증(개발은 빈값=개방 모드)

🚀 빠른 시작

cd lps

# 1) 설정 파일 준비 (DB·API 키 등)
cp config/config.local.toml.example config/config.local.toml   # 값 채우기

# 2) API 서버 실행 (요청 접수)
./run_local_server.sh              # → http://localhost:9600/docs

# 3) 워커 실행 (실제 검색 수행) — 별도 터미널
./run_local_worker.sh              # 대화형: 동시성·프로필 선택 (또는 python worker_main.py)

# (선택) 부하 테스트 GUI — Locust 웹 UI(:8089)
./run_loadtest_gui.sh              # 브라우저에서 users/spawn 조절하며 RPS/지연 관측

간단 테스트:

curl -X POST localhost:9600/v1/lps/search -H 'Content-Type: application/json' \
  -d '{"data":[{"product_code":"T1","product_name":"맥심 커피","specification":"1박스, 160개입"}]}'

서버 실행 — Docker

# 리포 루트에서 전체 스택과 함께 (권장)
docker compose up -d                # negosium 스택 + lps-api·lps-worker·lps-admin 모두 기동

# lps 서브셋만 (대화형: 설정 검증·guard 키 안전장치 + admin 포함)
./run_docker.sh

환경 구분이 없습니다(도메인 backend·negodata·agent 와 동일) — 항상 config.local.toml. prod 서버도 그 서버의 config.local.toml 에 prod 값(시크릿·guard 키·스케일)을 채우고 그냥 docker compose up -d. DB 는 컨테이너에서 host.docker.internal(호스트 DB)로 접속하고, 관리형 DB 면 LPS_DB_HOST= 로 override 를 끄고 toml 의 호스트를 씁니다. 운영에서 API 를 외부에 열지 않으려면 export LPS_API_BIND=127.0.0.1(리버스프록시 뒤).

자세한 실행/설정은 운영 가이드 참고.


📚 문서

문서 대상 내용
아키텍처 개발자/기획자 구성요소·파이프라인·안티봇(Akamai/Turnstile)·비용계측·동시성
데이터베이스 개발자/기획자 테이블 5종 구조와 코드값(+by_mall·ip_session)
API 사용법 연동 개발자 엔드포인트·요청/응답·metrics 예시
운영 가이드 운영자/개발자 실행·병렬·관측(readyz/ops/알림)·Docker 배포·문제 해결
크롤러 논의 오픈마켓 크롤러 유지 여부(ROI) 의사결정 메모

📁 폴더 구조

lps/
├── web_main.py            # API 서버 진입점 (요청 접수)
├── worker_main.py         # 워커 진입점 (검색+웜업+유휴정리+ops모니터)
├── Dockerfile             # API 이미지(lean) · Dockerfile.worker  # 워커(Chromium+Xvfb)
├── run_local_server.sh    # 로컬 API 실행 (대화형)
├── run_local_worker.sh    # 로컬 워커 실행 (대화형: 동시성·프로필)
├── run_docker.sh          # lps 서브셋 Docker 실행 (대화형: 설정 검증·guard 안전장치 + admin 포함)
├── run_loadtest_gui.sh    # 부하 테스트 Locust 웹 UI(:8089) 실행 (대화형)
├── config/                # 설정(config.local.toml — 포트/DB/API키, 미커밋; 배포는 env 주입)
├── common/                # 공통(enums, DB 세션, 모델, 로거, alerts=임계 알림 관리자)
│   └── database/model/models.py   # DB 테이블 정의
├── loadtest.py            # 부하 테스트 (N개 상품 → 처리량·지연·비용 집계)
├── crud/                  # DB 접근 (job_crud, price_history, negative_cache, bot_detection, ip_session)
├── services/
│   ├── search/            # 소스 어댑터 (coupang, naver, esm=G마켓·옥션, st11=11번가)
│   │   ├── browser_base.py    # patchright 공통(수명·프록시회전·차단감지·CDP 바이트계측)
│   │   ├── proxy.py           # DECODO(IP 회전·포트 쿨다운·프리플라이트)
│   │   └── card_parser.py     # 오픈마켓 공용 카드 파서
│   ├── pipeline/          # 필터·이상치·최저가 정렬(+몰별 분해)
│   ├── ai/                # AI 유사도 판정·검색어 생성 (OpenAI)
│   └── metrics.py         # 검색 원가 계측(AI/DECODO 비용·시간)
├── worker/                # 워커 루프·핸들러(폴백·데드라인)·알림(NOTIFY)
├── router/v1/lps/         # API 라우터
└── tests/                 # 테스트

포트

backend 9300 / negodata 9400 / agent 9500 과 겹치지 않게 LPS는 9600.