본문 바로가기
주전공/캡스톤디자인과창업프로젝트

여름방학 기록(7): DB 마이그레이션 체계(Alembic) 도입 및 초기 스키마 생성

by 희띠띠 2025. 8. 30.

8/29(금) - DB 마이그레이션(Alembic) 도입 및 초기 스키마 생성

오늘은 여름방학 중 마지막 회의날이다....

벌써개강이라니 난 마음의 준비가 안됐는걸,,,ㅜㅜ

 

이번 회차 작업 내용은 다음과 같다.

 

1. 배경

 

졸업 프로젝트에서 처음에는 Base.metadata.create_all(bind=engine) 방식으로 SQLite 데이터베이스를 생성해 사용했다.


하지만 이 방식은 단순히 테이블이 없으면 새로 생성할 뿐,

이후 모델 스키마가 변경될 경우(컬럼 추가/수정/삭제 등) 이를 추적하거나 자동 반영할 수 없었다.

 

팀 단위 협업에서는 데이터베이스 스키마가 변경될 때마다 팀원들이 같은 환경을 유지하기 어렵고,

수작업으로 DROP TABLE 후 다시 create_all()을 실행해야 하는 불편함이 있었다.

 

👉 따라서 Alembic이라는 마이그레이션 도구를 도입하여 DB 변경 이력을 코드로 관리하고,

버전 간 업그레이드/다운그레이드를 자동화하도록 수정함


2. Alembic 도입의 필요성

  • 스키마 변경 이력 관리 : 컬럼 추가/수정/삭제 기록을 리비전 파일로 남길 수 있음
  • 협업 효율 : 팀원 간 DB 구조를 동일하게 유지 가능 (alembic upgrade head 한 줄로 통일)
  • 롤백 지원 : 문제가 생기면 손쉽게 이전 버전으로 되돌릴 수 있음
  • 데이터 유지 : 기존 데이터는 보존하면서 스키마만 갱신 가능

3. 설치 및 초기 설정

sudo pip install alembic
alembic init migrations
  • 프로젝트 루트에 alembic.ini와 migrations/ 폴더가 생성됨
  • migrations/env.py 파일이 마이그레이션 실행의 핵심 스크립트

4. 환경 설정

4.1 .env 파일

데이터베이스 연결 문자열을 .env에서 관리하도록 설정했다.

# .env
ENVIRONMENT=dev
DATABASE_URL=sqlite:///./database.db
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
OPENAI_API_KEY=sk-xxxxxx
OPENAI_MODEL=gpt-4o-mini

 

4.2 app/database.py

SQLAlchemy 엔진과 세션 팩토리를 정의하고, .env의 DATABASE_URL을 읽어오도록 수정했습니다.

import os
from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./database.db")

kwargs = {}
if DATABASE_URL.startswith("sqlite"):
    kwargs["connect_args"] = {"check_same_thread": False}

engine = create_engine(DATABASE_URL, **kwargs)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

 

4.3 app/models/common_models.py

Alembic autogenerate 시 제약조건 이름이 매번 달라지는 문제를 막기 위해 Naming Convention을 도입했다.

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import MetaData

NAMING_CONVENTION = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(constraint_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}

class Base(DeclarativeBase):
    metadata = MetaData(naming_convention=NAMING_CONVENTION)

 

4.4 migrations/env.py

  • .env의 DATABASE_URL을 로드
  • SQLite에서 안전하게 ALTER 작업을 지원하기 위해 render_as_batch=True 설정
  • 모든 모델(market_models, festival_models, recommend_models)을 import하여 autogenerate 시 테이블 인식 보장
from alembic import context
from sqlalchemy import create_engine, pool
from dotenv import load_dotenv

from app.models.common_models import Base
import app.models.market_models
import app.models.festival_models
import app.models.recommend_models

load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./database.db")
IS_SQLITE = DATABASE_URL.startswith("sqlite")

target_metadata = Base.metadata

def run_migrations_online():
    connectable = create_engine(DATABASE_URL, poolclass=pool.NullPool)
    with connectable.connect() as connection:
        context.configure(
            connection=connection,
            target_metadata=target_metadata,
            compare_type=True,
            compare_server_default=True,
            render_as_batch=IS_SQLITE,
        )
        with context.begin_transaction():
            context.run_migrations()

 


5. 첫 마이그레이션 생성 및 적용

5.1 기존 DB 삭제 (개발용)

rm -f database.db

 

5.2 리비전 파일 생성

 
alembic revision --autogenerate -m "init schema"

 

출력 로그:

INFO  [alembic.autogenerate.compare] Detected added table 'categories'
INFO  [alembic.autogenerate.compare] Detected added table 'markets'
INFO  [alembic.autogenerate.compare] Detected added table 'products'
INFO  [alembic.autogenerate.compare] Detected added table 'festivals'
INFO  [alembic.autogenerate.compare] Detected added table 'regions'
INFO  [alembic.autogenerate.compare] Detected added table 'recommendations'
Generating /.../migrations/versions/cab00a13d2bb_init_schema.py ...  done

 

5.3 마이그레이션 적용

 
alembic upgrade head

 

출력 로그:

INFO  [alembic.runtime.migration] Running upgrade  -> cab00a13d2bb, init schema

→ database.db 안에 모든 테이블과 인덱스가 생성됨

 


6. 이후 워크플로

  • 모델 변경 → 새로운 리비전 생성
 
alembic revision --autogenerate -m "add column foo to products"
  • 적용
alembic upgrade head
  • 문제 발생 시 롤백
alembic downgrade -1

 


7. 팀 협업 규칙

  1. 모델 변경 시 반드시 Alembic 리비전 생성 후 Git에 푸시
  2. 팀원은 Pull 받은 후 alembic upgrade head 실행으로 DB 스키마 동기화
  3. 데이터는 seed_dev.py로 공통 샘플 세팅
  4. .env는 로컬에서 관리, 대신 .env.example를 Git에 포함시켜 공유

8. 정리

  • create_all()에서 Alembic으로 넘어오니 스키마 관리가 훨씬 체계적이고 팀 협업에 적합해졌음
  • SQLite에서는 render_as_batch=True가 필수라는 점, Pydantic v2 변경으로 인한 경고 처리 등을 경험하면서 실무에서 마이그레이션 세팅 시 주의해야 할 부분을 배웠음
  • 이제는 DB 구조 변경이 있더라도 팀원 전원이 쉽게 동일 환경을 유지할 수 있다는 안정감이 생김