fix: 대규모 Alpaca 백필 시 uvicorn 이벤트 루프 포화 방지

동시 Alpaca intraday 요청이 50개 이상 쌓이면 BaseHTTPMiddleware의
task 스케줄링 오버헤드로 이벤트 루프가 응답불능 상태가 되는 현상 수정.

- alpaca.py: _INTRADAY_SEMAPHORE(10) 추가 — /intraday, /intraday/today
  양쪽 핸들러를 감쌈. 11번째 이후 요청은 세마포어 대기(비용 없음)
- docker-compose.yml: --limit-concurrency 100 추가 — 100개 초과 시
  uvicorn이 503 반환, health 엔드포인트 보호

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 4 months ago
parent dd3e38fbf5
commit e25f7295a6

@ -2,6 +2,7 @@
Alpaca Market Data endpoints standalone price data via Alpaca API
"""
import asyncio
from datetime import date, datetime, timezone, timedelta
from typing import Optional
from zoneinfo import ZoneInfo
@ -11,6 +12,11 @@ from fastapi import APIRouter, HTTPException, Query
_ET = ZoneInfo("America/New_York")
_MARKET_CLOSE_HOUR = 16 # 4:00 PM ET
# Limit concurrent Alpaca intraday processing to prevent event-loop saturation
# under bulk backfill workloads. Callers beyond this limit wait on the semaphore
# (cheap asyncio wait) rather than flooding httpx connections and DB sessions.
_INTRADAY_SEMAPHORE = asyncio.Semaphore(10)
def _market_closed_for(d: date) -> bool:
"""Return True if the US equity market session for date d has ended."""
@ -120,6 +126,7 @@ async def get_alpaca_intraday_multi(
start_dt = datetime.combine(_start, datetime.min.time()).replace(tzinfo=timezone.utc)
end_dt = datetime.combine(_end, datetime.max.time()).replace(tzinfo=timezone.utc)
async with _INTRADAY_SEMAPHORE:
try:
data = await svc.get_or_fetch_multi_bars(
symbols, start_dt, end_dt, interval, force_refresh, feed="sip"
@ -193,6 +200,7 @@ async def get_alpaca_intraday_today(
start_dt = datetime.combine(today, datetime.min.time()).replace(tzinfo=timezone.utc)
end_dt = datetime.combine(today, datetime.max.time()).replace(tzinfo=timezone.utc)
async with _INTRADAY_SEMAPHORE:
try:
data = await svc.get_or_fetch_multi_bars(
symbols, start_dt, end_dt, interval, force_refresh=True, feed="iex"

@ -66,7 +66,7 @@ services:
- ./yfinance_plus:/app/yfinance_plus # Mount yfinance_plus for development
- ./data:/app/data # For data files
restart: unless-stopped
command: ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "18000", "--reload"]
command: ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "18000", "--reload", "--limit-concurrency", "100"]
# Frontend Application
frontend:

Loading…
Cancel
Save