레퍼런스의 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>
57 lines
2.8 KiB
Markdown
57 lines
2.8 KiB
Markdown
# 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`
|