레퍼런스의 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>
58 lines
1.8 KiB
Bash
Executable File
58 lines
1.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# 로컬 LPS 서버 실행 (대화형). 실행하면 모드를 골라 입력한다.
|
|
# 최초 실행 시 venv 생성 + 의존성 설치까지 자동으로 한다.
|
|
#
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")" # lps/
|
|
|
|
VENV=".venv"
|
|
PY="$VENV/bin/python"
|
|
PORT=9600
|
|
|
|
# 1) venv + 의존성 보장
|
|
if [[ ! -d "$VENV" ]]; then
|
|
echo "[setup] venv 생성 + 의존성 설치..."
|
|
python3 -m venv "$VENV"
|
|
"$PY" -m pip install -q --upgrade pip
|
|
"$PY" -m pip install -q -r requirements.txt
|
|
fi
|
|
|
|
# 2) config 보장
|
|
if [[ ! -f config/config.local.toml ]]; then
|
|
echo "[error] config/config.local.toml 이 없습니다. 아래로 생성 후 값을 채우세요:"
|
|
echo " cp config/config.local.toml.example config/config.local.toml"
|
|
exit 1
|
|
fi
|
|
|
|
# 3) 모드 선택
|
|
echo "── 실행 모드 선택 ──"
|
|
echo " 1) 일반 실행 (web_main.py)"
|
|
echo " 2) 자동 재시작 (uvicorn --reload, 개발용)"
|
|
echo " 3) 의존성 재설치"
|
|
echo " q) 취소"
|
|
read -rp "선택 [1]: " choice
|
|
choice="${choice:-1}"
|
|
|
|
case "$choice" in
|
|
3) echo "[setup] 의존성 재설치..."; "$PY" -m pip install -q -r requirements.txt; echo "완료"; exit 0 ;;
|
|
q|Q) echo "취소합니다."; exit 0 ;;
|
|
esac
|
|
|
|
# 4) 포트 정리 (이미 떠 있으면 종료)
|
|
if lsof -ti:"$PORT" >/dev/null 2>&1; then
|
|
echo "[info] 포트 $PORT 사용 중 → 기존 프로세스 종료"
|
|
lsof -ti:"$PORT" | xargs kill 2>/dev/null || true
|
|
sleep 1
|
|
fi
|
|
export APP_ENV=local
|
|
|
|
# 5) 실행
|
|
case "$choice" in
|
|
1) echo "[run] web_main.py → http://localhost:$PORT/docs"
|
|
exec "$PY" web_main.py ;;
|
|
2) echo "[run] uvicorn --reload → http://localhost:$PORT/docs"
|
|
exec "$VENV/bin/uvicorn" router.router:app --host 0.0.0.0 --port "$PORT" --reload ;;
|
|
*) echo "[error] 알 수 없는 선택: $choice"; exit 1 ;;
|
|
esac
|