o2o-negosium-original/lps/README.md
민헌 da9bd7750c feat(lps): 작업 큐 엔진 — PostgreSQL 원자적 claim + lease + dead-letter
레퍼런스의 DB-큐 반면교사를 전부 뒤집는 큐 엔진을 구축한다.
 - 원자적 claim: FOR UPDATE SKIP LOCKED 서브쿼리 + 같은 UPDATE + RETURNING
   → 워커 다수여도 이중 할당 원천 불가(fetch/claim 미분리)
 - 모든 전이는 조건부 CAS(status/worker_id 가드) + RETURNING
 - 복구는 timeout 추측이 아닌 lease 만료 소유권 기반(reaper 회수)
 - 재시도 지수백오프 + max_attempts + dead-letter 를 큐에 내장(스크립트 난립 제거)
 - dedupe_key 부분 유니크로 활성 중복 차단, counts()로 관측(수동 psql 대체)

- enums: JobStatus(PENDING/RUNNING/DONE/DEAD)·JobType(SEARCH/OUTBOX)
- models: job 테이블(무FK·SMALLINT코드·TIMESTAMPTZ, claim/lease/dedupe 인덱스)
- crud/job_crud: JobQueue(enqueue/claim/complete/fail/renew_lease/reap/counts)
- tests: 동시 8워커 이중할당0·lease회수·재시도→dead·소유권가드 등 8건(실 lps_db)

부수: 포트 9400→9600 (negodata 9400 충돌 회피)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:37:32 +09:00

57 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LPS (Lowest Price Search)
인터넷 최저가를 찾는 솔루션. **프레임워크는 `backend` 와 동일**하게 구성한 골격이며,
구체적인 도메인 로직(크롤링/오픈API 연동/최저가 산정 등)은 아직 정해지지 않았다.
## 스택 / 아키텍처 (backend 미러링)
- **FastAPI** 앱 (`router/router.py`) + `web_main.py` 부트스트랩
- **설정**: `config/` — TOML 로더(`config.local.toml`) + pydantic 모델, `APP_ENV`(기본 local)
- **DB**: `common/database/db_session_manager.py` — 논리 DB × Read/Write 엔진, service→람다 위임 패턴
- 엔진은 lazy 생성이라 **DB 없이도 부팅/healthz 동작**한다. 테이블·crud 가 생기면 그때 실제 접속.
- **공통 응답 규약**: `common/models/gmodel.py` — `Res_WebPacketProtocol.result`(성공/코드/설명)
- **결과 코드**: `common/enums.py` — `ErrorType`, `DBType`, `DBWRType`
- 계층 컨벤션: `router`(컨트롤러) → `services`(비즈니스) → `crud`(DB 접근)
## 폴더 구조
```
lps/
├── web_main.py # 진입점
├── requirements.txt / Dockerfile / .dockerignore
├── run_local_server.sh # 로컬 실행(대화형), 포트 9600
├── pytest.ini / conftest.py
├── config/
│ ├── config_loader.py / config_models.py / server_configs.py
│ └── config.local.toml.example # cp 해서 config.local.toml 로 사용(시크릿, 미커밋)
├── common/
│ ├── enums.py / logger.py / singleton.py
│ ├── utils/gtime.py
│ ├── models/gmodel.py # 프로토콜 base
│ └── database/{db_session_manager.py, model/models.py(MAIN_BASE)}
├── router/
│ ├── router.py # app + /healthz (도메인 라우터 미등록)
│ └── v1/validator/dependencies.py# RemoveNoneResponse
├── services/ # (비어있음) 도메인 서비스 추가 위치
├── crud/ # (비어있음) DB 접근 계층 추가 위치
└── tests/test_health.py # 스모크 테스트
```
## 로컬 실행
```bash
cp config/config.local.toml.example config/config.local.toml # 값 채우기
./run_local_server.sh # → http://localhost:9600/docs
```
## 테스트
```bash
python -m pytest # tests/ (기본 healthz 스모크)
```
## 포트
- backend 9300 / negodata 9400 / agent 9500 과 겹치지 않도록 **LPS 는 9600** 사용.
## 새 도메인 추가 순서 (backend 컨벤션)
1. `common/database/model/models.py` 에 테이블 정의(+ `DBType`)
2. `crud/<domain>_crud.py` (ABC 인터페이스 + 구현)
3. `services/<domain>_service.py` (비즈니스 로직)
4. `router/v1/<domain>/{protocol.py, <domain>.py}` 작성 후 `router/router.py` 에서 `include_router`