perf: API 성능 개선 — 로그 분석 기반 6개 항목 수정
P0 — 500 에러 대폭 감소 (1.1% → 0.11%)
- price.py / financial.py: except HTTPException: raise 추가 →
HTTPException(404)이 except Exception에 잡혀 500으로 재포장되던 버그 수정
- real_sec_financial_service: _get_price_data_for_period에 try/except 추가 →
yfinance 실패가 financial 엔드포인트 500으로 전파되지 않도록 방어
- price.py: TimeoutError 별도 핸들러 추가 → yfinance 타임아웃 시 503 반환
P1 — _store_price_data() N+1 → batch upsert
- price_data_service: 252회 개별 SELECT+INSERT 루프를
pg_insert(PriceData).on_conflict_do_nothing('uq_price_data') 단일 쿼리로 교체
P2 — financial/data 캐시 TTL 1h → 24h
- financial.py: 재무 데이터는 분기 발표 주기 → _FIN_TTL = 86400
P3 — 과거 가격 데이터 TTL 연장
- price.py: end_date < today-1 이면 TTL=7일, 나머지 1h 유지
P4 — 요청 로그 비동기 배치 처리
- error_logger.py: asyncio.Queue(10_000) 추가, _log_request를 put_nowait으로
변경 (논블로킹), 백그라운드 _request_log_flusher 코루틴 (1s/100건마다 flush)
- main.py: 앱 시작 시 start_request_log_flusher() 호출
P5 — 누락 DB 인덱스 추가
- financial.py: CalculatedMetrics에 calculation_date, period_date 단독 인덱스
- attention.py: AttentionFeaturesDaily에 ticker 단독 인덱스
- alembic b3c4d5e6f7a8: 위 3개 인덱스 생성 마이그레이션 (idempotent)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
main
parent
d03f46edbf
commit
6a98cb2d94
@ -0,0 +1,60 @@
|
|||||||
|
"""add performance indexes for CalculatedMetrics and AttentionFeaturesDaily
|
||||||
|
|
||||||
|
Revision ID: b3c4d5e6f7a8
|
||||||
|
Revises: a1b2c3d4e5f6
|
||||||
|
Create Date: 2026-03-19
|
||||||
|
|
||||||
|
Adds standalone indexes that were missing from the initial schema:
|
||||||
|
- calculated_metrics.calculation_date (date-only range queries)
|
||||||
|
- calculated_metrics.period_date (period-based lookups)
|
||||||
|
- attention_features_daily.ticker (ticker-only scans)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "b3c4d5e6f7a8"
|
||||||
|
down_revision: Union[str, Sequence[str], None] = "a1b2c3d4e5f6"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
|
||||||
|
# CalculatedMetrics — standalone date indexes
|
||||||
|
_indexes = conn.dialect.get_indexes(conn, "calculated_metrics")
|
||||||
|
existing = {idx["name"] for idx in _indexes}
|
||||||
|
|
||||||
|
if "idx_metrics_calculation_date" not in existing:
|
||||||
|
op.create_index(
|
||||||
|
"idx_metrics_calculation_date",
|
||||||
|
"calculated_metrics",
|
||||||
|
["calculation_date"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if "idx_metrics_period_date" not in existing:
|
||||||
|
op.create_index(
|
||||||
|
"idx_metrics_period_date",
|
||||||
|
"calculated_metrics",
|
||||||
|
["period_date"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# AttentionFeaturesDaily — standalone ticker index
|
||||||
|
_attn_indexes = conn.dialect.get_indexes(conn, "attention_features_daily")
|
||||||
|
existing_attn = {idx["name"] for idx in _attn_indexes}
|
||||||
|
|
||||||
|
if "idx_attention_features_daily_ticker" not in existing_attn:
|
||||||
|
op.create_index(
|
||||||
|
"idx_attention_features_daily_ticker",
|
||||||
|
"attention_features_daily",
|
||||||
|
["ticker"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_index("idx_metrics_calculation_date", table_name="calculated_metrics")
|
||||||
|
op.drop_index("idx_metrics_period_date", table_name="calculated_metrics")
|
||||||
|
op.drop_index("idx_attention_features_daily_ticker", table_name="attention_features_daily")
|
||||||
Loading…
Reference in New Issue