o2o-negosium-original/lps
민헌 4b8dee8583 perf(lps): Step1 — 폴백 데드라인 + fail-fast로 검색 지연 상한
G마켓 챌린지가 안 풀린 채 25~70초를 먹던 문제에 상한을 씌운다. 폴백은 '있으면 좋은'
보강이라 데드라인으로 잘라도 안전(그 몰만 스킵).

- handler: 각 폴백 크롤에 fallback_deadline_sec(기본 15s) wait_for. 초과 시 스킵.
  _timed_search 예외를 BaseException 로(CancelledError 계측 포함).
- 폴백 어댑터 max_block_retries=0(챌린지 대기 2배 방지), ESM ready_timeout 22→12s, st11 20→15s.
- 테스트: 느린 폴백 데드라인 스킵 1종.

실측(동시성2·4상품): 상품당 p50 84.6s→34.2s(~2.5x), 벽시계 181s→65s(~2.8x),
처리량 1.3→3.7 상품/분, 1000건 $7.12→$5.06(~29%↓, 실패 챌린지 대역폭 절감).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 17:01:09 +09:00
..
common feat(lps): price_history 몰별 최저가 스냅샷(by_mall JSONB) 2026-07-09 14:15:58 +09:00
config chore(lps): FE 원가 타일 AI/DECODO 비용 분해 + config example cost_per_gb 2026-07-09 15:34:57 +09:00
crud feat(lps): price_history 몰별 최저가 스냅샷(by_mall JSONB) 2026-07-09 14:15:58 +09:00
docs docs(lps): 운영 가이드 — WORKER_CONCURRENCY 병렬 실행 + loadtest.py 안내 2026-07-09 16:28:51 +09:00
migrations feat(lps): price_history 몰별 최저가 스냅샷(by_mall JSONB) 2026-07-09 14:15:58 +09:00
router feat(lps): price_history 몰별 최저가 스냅샷(by_mall JSONB) 2026-07-09 14:15:58 +09:00
services perf(lps): Step1 — 폴백 데드라인 + fail-fast로 검색 지연 상한 2026-07-09 17:01:09 +09:00
tests perf(lps): Step1 — 폴백 데드라인 + fail-fast로 검색 지연 상한 2026-07-09 17:01:09 +09:00
worker perf(lps): Step1 — 폴백 데드라인 + fail-fast로 검색 지연 상한 2026-07-09 17:01:09 +09:00
.dockerignore feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가 2026-07-08 15:18:27 +09:00
conftest.py feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가 2026-07-08 15:18:27 +09:00
Dockerfile feat(lps): 작업 큐 엔진 — PostgreSQL 원자적 claim + lease + dead-letter 2026-07-08 16:37:32 +09:00
loadtest.py feat(lps): 다중 상품 병렬 검색(워커별 브라우저 세트) + 부하테스트 스크립트 2026-07-09 16:28:08 +09:00
pytest.ini feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가 2026-07-08 15:18:27 +09:00
README.md docs(lps): 오픈마켓 폴백 크롤 반영 — 아키텍처·README 2026-07-09 14:54:00 +09:00
requirements.txt refactor(lps): 환경설정 TOML 단일화 — .env 제거, 시크릿도 config.local.toml 로 통합 2026-07-09 10:06:00 +09:00
run_local_server.sh feat(lps): 작업 큐 엔진 — PostgreSQL 원자적 claim + lease + dead-letter 2026-07-08 16:37:32 +09:00
web_main.py feat(lps): 작업 큐 엔진 — PostgreSQL 원자적 claim + lease + dead-letter 2026-07-08 16:37:32 +09:00
worker_main.py perf(lps): Step1 — 폴백 데드라인 + fail-fast로 검색 지연 상한 2026-07-09 17:01:09 +09:00

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

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


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

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

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


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

 [1] 검색 요청           [2] 대기줄(큐)         [3] 일꾼(워커)가 처리
  상품 정보 전송   ─────▶  순서대로 쌓임   ─────▶  네이버 + 쿠팡 동시 검색
 (POST /search)          (즉시 접수번호 반환)         │
                                                     ▼
                                        [4] 걸러내기 + AI 판정
                                        가격 이상치 제거 → "같은 상품"만 선별
                                                     │
                                                     ▼
                                        [5] 최저가 확정 + 기록
                                        네이버/쿠팡/최종 최저가 저장 → 그래프용 이력 적재
                                                     │
                                                     ▼
                                        [6] 완료(결과 조회 가능)

핵심 포인트

  • 즉시 응답 + 나중 처리: 요청하면 바로 "접수번호(job_id)"를 주고, 실제 검색은 뒤에서 진행됩니다. (검색은 몇 초~수십 초 걸림)
  • 못 찾으면 검색어를 바꿔 재시도: "맥심 커피"로 안 나오면 "맥심 모카골드 커피믹스"처럼 AI가 검색어를 다듬어 다시 시도하고, 그래도 없으면 "없음"으로 정리합니다. (무한 재시도 안 함)
  • 차단 대응: 쿠팡이 봇으로 감지하면 다른 IP로 바꿔 재시도하고, 감지 이력도 기록합니다.

✨ 주요 기능

기능 설명
멀티 소스 검색 네이버 쇼핑 API + 쿠팡(봇 차단 우회) 동시 검색·병합
AI 같은 상품 판정 "진짜 그 상품"만 선별 (액세서리·다른 규격 제외)
검색어 자동 정제 0건이면 정밀/광역 검색어로 재시도
최저가 이력 그래프 조회 시점마다 네이버/쿠팡/최종 최저가를 시계열로 기록
안정적 큐 처리 작업 유실 없이 순서대로, 실패 시 자동 재시도
프록시 IP 회전 차단 회피용 IP 자동 순환(DECODO)

🚀 빠른 시작

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) 워커 실행 (실제 검색 수행) — 별도 터미널
python worker_main.py

간단 테스트:

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

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


📚 문서

문서 대상 내용
아키텍처 개발자/기획자 구성요소, 처리 파이프라인, 재시도·프록시·AI 동작 원리
데이터베이스 개발자/기획자 테이블 4종 구조와 코드값
API 사용법 연동 개발자 엔드포인트·요청/응답 예시(Postman/curl)
운영 가이드 운영자/개발자 실행·로그 보기·DB 조회·문제 해결

📁 폴더 구조

lps/
├── web_main.py            # API 서버 진입점 (요청 접수)
├── worker_main.py         # 워커 진입점 (실제 검색 수행)
├── run_local_server.sh    # 로컬 API 실행 스크립트
├── config/                # 설정(config.local.toml — 포트/DB/API키, 미커밋)
├── common/                # 공통(enums, DB 세션, 모델, 로거)
│   └── database/model/models.py   # DB 테이블 정의
├── crud/                  # DB 접근 (job_crud, price_history, negative_cache, bot_detection)
├── services/
│   ├── search/            # 소스 어댑터 (coupang, naver, esm=G마켓·옥션, st11=11번가) + 프록시·필터
│   ├── pipeline/          # 필터·이상치·최저가 정렬
│   └── ai/                # AI 유사도 판정·검색어 생성 (OpenAI)
├── worker/                # 워커 루프·핸들러·알림(NOTIFY)
├── router/v1/lps/         # API 라우터
└── tests/                 # 테스트

포트

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