fix: 서버 응답불능 — limit-concurrency 20 + asyncio yield points

BaseHTTPMiddleware는 요청당 2개 asyncio task를 생성한다.
--limit-concurrency 100은 여전히 200개 task가 event loop를 포화시켜
health check 포함 모든 요청이 block됐다.

변경 사항:
- docker-compose.yml: --limit-concurrency 100 → 20
  (세마포어 10개 처리 + 10개 대기, 나머지 20초과 시 503으로 즉시 반환)
- alpaca_price_service.py: upsert 루프 청크 사이 + Phase 4 ORM 일괄 생성 후
  await asyncio.sleep(0) 추가 — 동기 CPU 블로킹 중 event loop yield 보장

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

@ -2,6 +2,7 @@
Alpaca bars AlpacaPriceData conversion and DB storage Alpaca bars AlpacaPriceData conversion and DB storage
""" """
import asyncio
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, List, Optional from typing import Dict, List, Optional
@ -84,6 +85,7 @@ class AlpacaPriceService:
stmt = stmt.on_conflict_do_nothing(constraint='uq_alpaca_price_data') stmt = stmt.on_conflict_do_nothing(constraint='uq_alpaca_price_data')
result = await db.execute(stmt) result = await db.execute(stmt)
inserted += result.rowcount inserted += result.rowcount
await asyncio.sleep(0) # yield event loop between chunks
await db.commit() await db.commit()
if inserted: if inserted:
@ -183,6 +185,7 @@ class AlpacaPriceService:
stmt = pg_insert(AlpacaPriceData).values(rows[i : i + CHUNK]) stmt = pg_insert(AlpacaPriceData).values(rows[i : i + CHUNK])
stmt = stmt.on_conflict_do_nothing(constraint="uq_alpaca_price_data") stmt = stmt.on_conflict_do_nothing(constraint="uq_alpaca_price_data")
await db.execute(stmt) await db.execute(stmt)
await asyncio.sleep(0) # yield event loop between chunks
await db.commit() await db.commit()
logger.info( logger.info(
f"Alpaca multi-bars: stored {len(rows)} rows for {len(need_fetch)} tickers" f"Alpaca multi-bars: stored {len(rows)} rows for {len(need_fetch)} tickers"
@ -203,6 +206,7 @@ class AlpacaPriceService:
.order_by(AlpacaPriceData.ticker, AlpacaPriceData.date) .order_by(AlpacaPriceData.ticker, AlpacaPriceData.date)
) )
db_rows = result.scalars().all() db_rows = result.scalars().all()
await asyncio.sleep(0) # yield after bulk ORM object creation
data: Dict[str, List[AlpacaPriceData]] = {t: [] for t in upper_tickers} data: Dict[str, List[AlpacaPriceData]] = {t: [] for t in upper_tickers}
for row in db_rows: for row in db_rows:

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

Loading…
Cancel
Save