로컬‐개발‐환경‐세팅 - 100-hours-a-week/KTB4-13th-wiki GitHub Wiki

로컬 개발 환경 세팅

담당자: Sevan · 상태: ✅ 검증됨 · 최종 수정: 2026-09-17

AI 서버를 로컬에서 띄우기까지의 절차다. 위에서부터 그대로 따라 하면 ⑧ /health가 동작하는 상태가 된다.


목차

  1. 준비물
  2. 프로젝트 초기화
  3. 로컬 DB
  4. 스키마 적용
  5. 환경변수
  6. 실행과 확인
  7. 세팅 중 확정한 것
  8. 자주 겪는 문제

1. 준비물

항목 버전 비고
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)에 가둔다.


2. 프로젝트 초기화

git clone <AI 저장소> && cd KTB4-13th-AI
uv sync

uv sync는 uv.lock에 박힌 정확한 버전을 그대로 설치한다. 팀원 간 환경이 어긋나지 않는 근거가 이 파일이다.

새로 시작하는 경우는 아래와 같다.

uv init --python 3.12 --name bookjeok-ai
uv add fastapi "uvicorn[standard]" pydantic-settings asyncpg
uv add --dev pytest ruff

ruff·pytest는 취향이 아니라 CI 계약이다. AI 저장소의 PR 파이프라인이 Lint → Test라 둘이 없으면 CI가 돌지 않는다.


3. 로컬 DB

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로 시작해도 된다

4. 스키마 적용

psql -v ON_ERROR_STOP=1 -d bookjeok_ai_dev -f db/migrations/001_init.sql

ON_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가 FK ON DELETE CASCADE로 전량 삭제된다. 로컬에서 실측으로 확인했다(2건 → 0건). 카탈로그 갱신은 반드시 행 단위 upsert/delete로 해야 한다.


5. 환경변수

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에서 이미 고정된 것을 따른다 (HOST PORT EMBEDDING_MODEL WORKER_COUNT EXTERNAL_AI_API_KEY TZ)
  • 운영 비밀값은 SSM Parameter Store(SecureString)로 관리한다
  • 새 환경변수를 추가하면 반드시 .env.example에도 반영한다. 팀원 간 유일한 설정 공유 수단이다

6. 실행과 확인

uv run uvicorn app.main:app --reload --port 8000
curl 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에서 볼 수 있다.


7. 세팅 중 확정한 것

세부 근거는 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에 추가 필요

8. 자주 겪는 문제

[Errno 48] Address already in use

다른 프로세스가 포트를 쓰고 있다. 범인을 먼저 찾는다.

lsof -nP -iTCP:8000 -sTCP:LISTEN

나온 PID를 종료하거나, 급하면 --port 8001로 우회한다. 다만 운영 포트가 8000으로 고정이라 로컬도 맞추는 편이 낫다.

ConnectionRefusedError → Application startup failed

DB가 떠 있지 않다. 앱은 DB 없이 기동을 거부하도록 되어 있으므로 정상 동작이다.

pg_isready

brew services start가 "already started"라는데 접속이 안 될 때

brew의 상태 정보와 실제 프로세스가 어긋난 경우다. 순서대로 확인한다.

brew services list      # 상태가 none 이면 실제로는 꺼진 것
pgrep -fl "postgres -D" # 프로세스 존재 확인

restart로 푼다.

brew services restart postgresql@17

앱이 DATABASE_URL 없다고 뜨지 않을 때

정상이다. 필수 환경변수가 없으면 기동 시점에 실패하도록 되어 있다. 한참 뒤 첫 요청에서 터지는 것보다 낫다. .env를 확인한다.


다음 단계

순서 작업 데이터 필요
1 예외 핸들러 · 로깅 미들웨어 —
2 ② /embeddings —
3 카탈로그 적재 (57만 권) —
4 ① /search 3번 완료 후
5 ⑥ /profile → ④ /feed 가짜 사용자 필요
6 ③ /chat LLM 키

← AI Wiki

⚠️ **GitHub.com Fallback** ⚠️