로컬‐개발‐환경‐세팅 - 100-hours-a-week/KTB4-13th-wiki GitHub Wiki
담당자: Sevan · 상태: ✅ 검증됨 · 최종 수정: 2026-09-17
AI 서버를 로컬에서 띄우기까지의 절차다. 위에서부터 그대로 따라 하면 ⑧ /health가 동작하는 상태가 된다.
| 항목 | 버전 | 비고 |
|---|---|---|
| Python | 3.12 | 시스템에 3.14가 있어도 3.12를 쓴다. torch·sentence-transformers 휠 호환 때문 |
| uv | 최신 | 파이썬 버전과 의존성을 함께 관리한다 |
| PostgreSQL | 17 | pgvector 확장 포함 |
| pgvector | 0.8.x | brew install pgvector |
| Docker | 선택 | 운영과 같은 구성으로 띄울 때만 |
Python 3.12는 따로 설치하지 않아도 된다. uv가 받아서 프로젝트 전용 가상환경(.venv)에 가둔다.
git clone <AI 저장소> && cd KTB4-13th-AI
uv syncuv sync는 uv.lock에 박힌 정확한 버전을 그대로 설치한다. 팀원 간 환경이 어긋나지 않는 근거가 이 파일이다.
새로 시작하는 경우는 아래와 같다.
uv init --python 3.12 --name bookjeok-ai
uv add fastapi "uvicorn[standard]" pydantic-settings asyncpg
uv add --dev pytest ruffruff·pytest는 취향이 아니라 CI 계약이다. AI 저장소의 PR 파이프라인이 Lint → Test라 둘이 없으면 CI가 돌지 않는다.
createdb bookjeok_ai_dev
psql -d bookjeok_ai_dev -c "ALTER DATABASE bookjeok_ai_dev SET timezone = 'Asia/Seoul'"
psql -d bookjeok_ai_dev -c "CREATE EXTENSION vector; CREATE EXTENSION pg_trgm;"확인:
psql -d bookjeok_ai_dev -c "SHOW timezone" -c "\dx"Asia/Seoul, vector, pg_trgm이 보이면 정상이다.
- 확장은 DB마다 따로 켜야 한다. 설치는 서버 전체지만 활성화는 DB 단위다
-
vector= 벡터 저장·유사도 검색,pg_trgm= 한국어 부분 일치 검색 - 운영은
pgvector/pgvector:pg17컨테이너를 쓴다. 로컬은 편의상 Homebrew Postgres로 시작해도 된다
psql -v ON_ERROR_STOP=1 -d bookjeok_ai_dev -f db/migrations/001_init.sqlON_ERROR_STOP=1이 없으면 psql이 오류를 무시하고 계속 진행한다. 절반만 만들어진 DB로 개발을 시작하게 되므로 반드시 붙인다. 파일이 BEGIN~COMMIT으로 감싸여 있어 실패하면 통째로 롤백된다.
적용 결과는 테이블 8개 · 인덱스 17개다.
| 구분 | 테이블 |
|---|---|
| BE 복제 사본 (읽기 전용) |
v_books v_book_popularity v_user_purchases v_user_library v_user_reviews
|
| AI 소유 (쓰기) |
book_embeddings taste_profile idempotency_records
|
확인:
psql -d bookjeok_ai_dev -c "\dt"주의.
v_books를TRUNCATE하거나DROP후 재생성하면book_embeddings가 FKON DELETE CASCADE로 전량 삭제된다. 로컬에서 실측으로 확인했다(2건 → 0건). 카탈로그 갱신은 반드시 행 단위 upsert/delete로 해야 한다.
cp .env.example .env.env에서 최소한 DATABASE_URL만 채우면 ⑧까지 돌아간다.
DATABASE_URL=postgresql://<계정>@127.0.0.1:5432/bookjeok_ai_dev
-
.env는 커밋하지 않는다..gitignore에 들어 있다 -
.env.example은 커밋한다. 키 이름과 설명만 두고 실제 값은 넣지 않는다 - 변수 이름은 클라우드 파트
compose.ai.yml에서 이미 고정된 것을 따른다 (HOSTPORTEMBEDDING_MODELWORKER_COUNTEXTERNAL_AI_API_KEYTZ) - 운영 비밀값은 SSM Parameter Store(SecureString)로 관리한다
- 새 환경변수를 추가하면 반드시
.env.example에도 반영한다. 팀원 간 유일한 설정 공유 수단이다
uv run uvicorn app.main:app --reload --port 8000curl http://127.0.0.1:8000/health정상 응답:
{
"status": "degraded",
"version": "dev",
"replication_lag_seconds": null,
"components": {
"gateway": "ok",
"database": "ok",
"vector_index": "ok",
"embedding": "unavailable",
"llm": "unavailable"
}
}degraded가 정상이다. ②·③을 아직 만들지 않아 embedding·llm이 unavailable이기 때문이다. 배포 판정은 ok·degraded 둘 다 통과이고, down(503)일 때만 트래픽에서 뺀다.
database·vector_index는 실제로 DB에 질의해 확인한다. Postgres를 내리면 unavailable로 바뀐다.
자동 생성된 API 문서는 http://127.0.0.1:8000/docs에서 볼 수 있다.
세부 근거는 AI 저장소의 docs/개발-로그.md에 있다.
| 항목 | 결정 | 이유 |
|---|---|---|
| 벡터 저장소 | PostgreSQL + pgvector | 가격·재고 필터(WHERE)와 의미 유사도 정렬(ORDER BY)을 한 SQL로 처리해야 한다. 전용 벡터 DB는 저장소 2개 동기화 + 질의 2단계 분리가 생긴다 |
| 임베딩 모델 | multilingual-e5-small (384차원) | 쿼리 p95 5.8ms · 151 docs/s. bge-m3(1024)는 처리량이 8분의 1 |
| 시각 | timestamptz 타입 + KST 기준 |
한국은 서머타임이 없어 KST = UTC+9 고정. 중요한 건 타임존이 아니라 타입 — timestamp(tz 없음)를 쓰면 값이 섞이는 순간 조용히 깨진다 |
| 별점 |
numeric(2,1) (ERD의 int와 다름) |
BE 원본이 DECIMAL(3,1) 0.5단위다. 정수로 받으면 3.5점이 3점으로 뭉개진다. 취향 계산 경계를 4.0↑ 선호 / 2.0↓ 비선호로 다시 그었다 |
| 이력 3종 PK | 걸지 않음 | ERD가 명시하지 않았고, (user_id, book_id) 유니크를 걸면 재구매 행이 조용히 유실된다 |
| Python | 3.12 |
torch·sentence-transformers 휠 호환 |
| 안건 | 현재 | 필요한 것 |
|---|---|---|
| 벡터 DB |
compose.ai.yml이 qdrant/qdrant:v1.18.2
|
pgvector Postgres로 교체. QDRANT_* 환경변수도 Postgres DSN으로 |
| 복제 경로 |
replicator 컨테이너 없음. MySQL이 db_net(internal: true)에 있어 AI EC2에서 갈 경로가 없다 |
복제기 추가 + BE MySQL 읽기 전용 계정 |
| 메모리 | AI EC2가 t3.small(2 GiB) |
4GB 이상. 임베딩 컨테이너만 실측 1.4GB, 상시 합계 약 2.8GB |
| 헬스체크 | "초기 POC에서 제외"로 기재 | CD 자동 롤백 게이트가 헬스체크다. ⑧ 기반으로 compose에 추가 필요 |
다른 프로세스가 포트를 쓰고 있다. 범인을 먼저 찾는다.
lsof -nP -iTCP:8000 -sTCP:LISTEN나온 PID를 종료하거나, 급하면 --port 8001로 우회한다. 다만 운영 포트가 8000으로 고정이라 로컬도 맞추는 편이 낫다.
DB가 떠 있지 않다. 앱은 DB 없이 기동을 거부하도록 되어 있으므로 정상 동작이다.
pg_isreadybrew의 상태 정보와 실제 프로세스가 어긋난 경우다. 순서대로 확인한다.
brew services list # 상태가 none 이면 실제로는 꺼진 것
pgrep -fl "postgres -D" # 프로세스 존재 확인restart로 푼다.
brew services restart postgresql@17정상이다. 필수 환경변수가 없으면 기동 시점에 실패하도록 되어 있다. 한참 뒤 첫 요청에서 터지는 것보다 낫다. .env를 확인한다.
| 순서 | 작업 | 데이터 필요 |
|---|---|---|
| 1 | 예외 핸들러 · 로깅 미들웨어 | — |
| 2 | ② /embeddings
|
— |
| 3 | 카탈로그 적재 (57만 권) | — |
| 4 | ① /search
|
3번 완료 후 |
| 5 | ⑥ /profile → ④ /feed
|
가짜 사용자 필요 |
| 6 | ③ /chat
|
LLM 키 |