from typing import AsyncGenerator from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase from config import db_settings class Base(DeclarativeBase): pass # ============================================================================= # 메인 엔진 (FastAPI 요청용) # ============================================================================= engine = create_async_engine( url=db_settings.MYSQL_URL, echo=False, pool_size=20, # 기본 풀 크기: 20 max_overflow=20, # 추가 연결: 20 (총 최대 40) pool_timeout=30, # 풀에서 연결 대기 시간 (초) pool_recycle=3600, # 1시간마다 연결 재생성 pool_pre_ping=True, # 연결 유효성 검사 pool_reset_on_return="rollback", # 반환 시 롤백으로 초기화 connect_args={ "connect_timeout": 10, # DB 연결 타임아웃 "charset": "utf8mb4", }, ) # 메인 세션 팩토리 (FastAPI DI용) AsyncSessionLocal = async_sessionmaker( bind=engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, # 명시적 flush 권장 ) # ============================================================================= # 백그라운드 태스크 전용 엔진 (메인 풀과 분리) # ============================================================================= background_engine = create_async_engine( url=db_settings.MYSQL_URL, echo=False, pool_size=10, # 백그라운드용 풀 크기: 10 max_overflow=10, # 추가 연결: 10 (총 최대 20) pool_timeout=60, # 백그라운드는 대기 시간 여유있게 pool_recycle=3600, pool_pre_ping=True, pool_reset_on_return="rollback", connect_args={ "connect_timeout": 10, "charset": "utf8mb4", }, ) # 백그라운드 세션 팩토리 BackgroundSessionLocal = async_sessionmaker( bind=background_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, ) async def create_db_tables(): import asyncio # 모델 import (테이블 메타데이터 등록용) from app.home.models import Image, Project # noqa: F401 from app.lyric.models import Lyric # noqa: F401 from app.song.models import Song # noqa: F401 from app.video.models import Video # noqa: F401 print("Creating database tables...") async with asyncio.timeout(10): async with engine.begin() as connection: await connection.run_sync(Base.metadata.create_all) # FastAPI 의존성용 세션 제너레이터 async def get_session() -> AsyncGenerator[AsyncSession, None]: # 커넥션 풀 상태 로깅 (디버깅용) pool = engine.pool print(f"[get_session] Pool status - size: {pool.size()}, checked_in: {pool.checkedin()}, checked_out: {pool.checkedout()}, overflow: {pool.overflow()}") async with AsyncSessionLocal() as session: try: yield session except Exception as e: await session.rollback() print(f"[get_session] Session rollback due to: {e}") raise e finally: # 명시적으로 세션 종료 확인 print(f"[get_session] Session closing - Pool checked_out: {pool.checkedout()}") # 앱 종료 시 엔진 리소스 정리 함수 async def dispose_engine() -> None: await engine.dispose() await background_engine.dispose() print("Database engines disposed (main + background)")