728x90
반응형

FastAPI Hot Reload - Sqlmodel Metadata Reload Issue

FastAPI에서 sys.modules 조작이 잘 유지된 상태라고 가정하더라도, SQLModelmetadata 초기화 문제가 리로드 시 발생할 가능성이 있습니다.
이는 SQLModel(SQLAlchemy 기반)이 FastAPI 애플리케이션의 재시작과 함께 metadata 객체를 초기화하기 때문입니다.


문제 분석

문제의 원인: SQLModel의 metadata 초기화

SQLModel은 기본적으로 sqlmodel.sqlalchemy.MetaData 객체를 사용해 테이블 스키마를 관리합니다. Uvicorn의 --reload가 작동하면 Python 프로세스가 재시작되고, 이에 따라 다음 문제가 발생할 수 있습니다:

  1. 테이블 등록 정보 손실: 리로드로 인해 SQLModel.metadata가 초기화되면서 기존에 등록된 테이블 정보가 사라집니다.
  2. 재등록 문제: 애플리케이션이 다시 시작되면서 동일한 테이블이 재등록되거나, 기존 테이블과 충돌이 발생할 수 있습니다.
  3. 세션 관리 문제: 기존 Session 또는 engine 객체가 유효하지 않게 되어, 데이터베이스 작업이 실패할 가능성이 있습니다.

문제 확인 방법

다음 사항을 점검하여 리로드 문제인지 확인하세요:

  1. metadata 초기화 확인
    리로드 시 SQLModel.metadata 객체가 초기화되는지 확인합니다.

    from sqlmodel import SQLModel
    
    print(f"Tables in metadata: {SQLModel.metadata.tables.keys()}")

    리로드 후 테이블 목록이 빈 값으로 출력된다면 metadata가 초기화된 것입니다.

  2. 리로드 시 에러 확인
    리로드 후 로그에서 다음과 같은 에러를 확인합니다:

    • sqlalchemy.exc.NoSuchTableError
    • sqlalchemy.exc.InvalidRequestError: Table already exists
  3. 테이블 중복 정의 확인
    리로드 이후 테이블이 중복으로 정의되거나, 데이터베이스에 충돌이 발생하는지 확인합니다.


문제 해결 방안

리로드 시 SQLModel.metadata가 초기화되는 문제를 방지하거나 복구하기 위해 다음 방법을 사용할 수 있습니다.


1. metadata 재등록 로직 추가

리로드 시 SQLModel.metadata가 초기화되었다면, 이를 다시 설정해주는 로직을 추가합니다. FastAPI 애플리케이션 초기화 시, metadata를 재등록하도록 설정합니다.

from sqlmodel import SQLModel, create_engine

# 엔진 생성
DATABASE_URL = "sqlite:///example.db"
engine = create_engine(DATABASE_URL)

# 테이블 재등록
def register_metadata():
    # 모든 모델에서 테이블을 재등록
    SQLModel.metadata.create_all(bind=engine)
    print("[INFO] Metadata registered!")

# FastAPI 앱 초기화
from fastapi import FastAPI

app = FastAPI()

@app.on_event("startup")
def on_startup():
    register_metadata()

@app.get("/")
def read_root():
    return {"message": "Metadata reloaded successfully!"}

2. metadata 충돌 방지

SQLAlchemy가 동일한 테이블을 두 번 정의하지 않도록 방지하려면, 테이블 정의 전에 metadata 객체를 초기화하는 조건을 추가합니다.

from sqlmodel import SQLModel

# 초기화 방지
if not SQLModel.metadata.tables:
    SQLModel.metadata.clear()

이 코드로 테이블 중복 정의를 방지할 수 있습니다.


3. sys.modules 활용하여 상태 유지

리로드 시에도 SQLModel.metadata를 유지하기 위해 sys.modules를 활용해 테이블 상태를 저장할 수 있습니다.

import sys
from sqlmodel import SQLModel

# 기존 metadata 유지
if "app_metadata" in sys.modules:
    SQLModel.metadata = sys.modules["app_metadata"]
else:
    sys.modules["app_metadata"] = SQLModel.metadata

정리

리로드로 인해 SQLModel.metadata가 초기화되는 문제는 다음과 같이 해결할 수 있습니다:

  1. metadata 재등록: FastAPI의 startup 이벤트에서 SQLModel.metadata.create_all()을 호출.
  2. 테이블 중복 방지: 리로드 전에 metadata를 초기화하거나, sys.modules를 활용하여 상태를 유지.

이 접근 방식으로 리로드 시 발생하는 SQLModel의 문제를 안정적으로 해결할 수 있습니다.

728x90
반응형

+ Recent posts