fix(bootstrap): backend lifespan 에서 DB migrate + symbols 시드 자동화
- main.py 의 lifespan 시작 시 idempotent migration 적용 + symbols 비어있으면 pykrx 로 전 종목 시드 - BOOTSTRAP_DISABLED=1 / SCHEDULER_DISABLED=1 env 로 비활성 가능 (테스트 용) - 실패해도 서버는 뜨고 /health/db 가 진단 제공 리뷰어 지적 1번 (cold-start 시 /api/refresh 404) 해결. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
from app.api.chart import router as chart_router
|
from app.api.chart import router as chart_router
|
||||||
from app.api.metrics import router as metrics_router
|
from app.api.metrics import router as metrics_router
|
||||||
@@ -13,7 +15,7 @@ from app.api.predict import router as predict_router
|
|||||||
from app.api.refresh import router as refresh_router
|
from app.api.refresh import router as refresh_router
|
||||||
from app.api.symbols import router as symbols_router
|
from app.api.symbols import router as symbols_router
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.db.connection import ping as db_ping
|
from app.db.connection import get_engine, ping as db_ping
|
||||||
from app.fetch import dart as dart_mod
|
from app.fetch import dart as dart_mod
|
||||||
from app.fetch import kis as kis_mod
|
from app.fetch import kis as kis_mod
|
||||||
from app.pipelines.scheduler import shutdown_scheduler, start_scheduler
|
from app.pipelines.scheduler import shutdown_scheduler, start_scheduler
|
||||||
@@ -25,13 +27,55 @@ logging.basicConfig(
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _bootstrap_db() -> None:
|
||||||
|
"""첫 부팅 자동화:
|
||||||
|
1) migrations/*.sql idempotent 적용 (timescale/pgvector 확장 + 스키마)
|
||||||
|
2) symbols 테이블 비어있으면 pykrx 로 전 종목 시드 (SEED 10 마크 포함)
|
||||||
|
|
||||||
|
BOOTSTRAP_DISABLED=1 이면 스킵 (테스트/CI 용). 어떤 단계든 실패해도 서버는
|
||||||
|
뜬다 — /health/db 가 진단을 알려준다.
|
||||||
|
"""
|
||||||
|
if os.environ.get("BOOTSTRAP_DISABLED") == "1":
|
||||||
|
logger.info("bootstrap skipped (BOOTSTRAP_DISABLED=1)")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 1) migrations
|
||||||
|
try:
|
||||||
|
from app.db.migrate import apply_all
|
||||||
|
res = apply_all()
|
||||||
|
logger.info("bootstrap migrate: %s", res)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
logger.exception("bootstrap migrate failed")
|
||||||
|
return # 스키마 없으면 시드 불가
|
||||||
|
|
||||||
|
# 2) symbols 시드 (비어있을 때만 — pykrx 호출이 비싸므로 항상 돌리지 않음)
|
||||||
|
try:
|
||||||
|
eng = get_engine()
|
||||||
|
with eng.connect() as conn:
|
||||||
|
row = conn.execute(text("SELECT COUNT(*) FROM symbols")).first()
|
||||||
|
count = int(row[0]) if row else 0
|
||||||
|
if count == 0:
|
||||||
|
logger.info("symbols empty — running initial seed")
|
||||||
|
from app.fetch.symbols_seed import seed_symbols
|
||||||
|
report = seed_symbols()
|
||||||
|
logger.info("bootstrap seed_symbols: %s", report)
|
||||||
|
else:
|
||||||
|
logger.info("symbols already populated (count=%d) — skip seed", count)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
logger.exception("bootstrap seed_symbols failed")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(_: FastAPI):
|
async def lifespan(_: FastAPI):
|
||||||
|
_bootstrap_db()
|
||||||
# 스케줄러는 옵션. CI/테스트에서 disable 하고 싶으면 SCHEDULER_DISABLED 같은 env 추가 가능.
|
# 스케줄러는 옵션. CI/테스트에서 disable 하고 싶으면 SCHEDULER_DISABLED 같은 env 추가 가능.
|
||||||
try:
|
if os.environ.get("SCHEDULER_DISABLED") == "1":
|
||||||
start_scheduler()
|
logger.info("scheduler skipped (SCHEDULER_DISABLED=1)")
|
||||||
except Exception: # noqa: BLE001
|
else:
|
||||||
logger.exception("scheduler start failed")
|
try:
|
||||||
|
start_scheduler()
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
logger.exception("scheduler start failed")
|
||||||
yield
|
yield
|
||||||
shutdown_scheduler()
|
shutdown_scheduler()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user