자산API DB캐쉬 사용
This commit is contained in:
@@ -488,6 +488,81 @@ async def get_portfolio_summary(db: AsyncSession = Depends(get_db)):
|
||||
})
|
||||
return resp
|
||||
|
||||
async def _sync_portfolio_background():
|
||||
"""백그라운드 포트폴리오 KIS 동기화 (비차단). 이미 동기화 중이면 건너뜀."""
|
||||
if portfolio_cache.sync_lock.locked():
|
||||
return
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
await sync_portfolio(db, force=False)
|
||||
except Exception as e:
|
||||
logger.debug(f"Background portfolio sync: {e}")
|
||||
|
||||
|
||||
def _build_combined_resp() -> dict:
|
||||
"""portfolio_cache에서 combined 응답 딕셔너리 생성"""
|
||||
base = portfolio_cache.summary or {
|
||||
"total_stocks": 0, "total_value": 0, "total_investment": 0,
|
||||
"total_profit_loss": 0, "total_profit_loss_rate": 0,
|
||||
"available_cash": 0, "total_assets": 0,
|
||||
}
|
||||
resp = dict(base)
|
||||
resp.update({
|
||||
"sync_status": "ok" if portfolio_cache.last_error is None else "error",
|
||||
"last_synced": portfolio_cache.last_synced.isoformat() if portfolio_cache.last_synced else None,
|
||||
"last_error": portfolio_cache.last_error,
|
||||
})
|
||||
return {
|
||||
"holdings": [h for h in portfolio_cache.holdings if h.quantity > 0],
|
||||
"summary": resp,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/combined")
|
||||
async def get_portfolio_combined(db: AsyncSession = Depends(get_db)):
|
||||
"""보유종목 목록 + 요약을 반환.
|
||||
|
||||
응답 우선순위:
|
||||
① 신선한 캐시 → 즉시 반환
|
||||
② 캐시 만료 + 매매 변경 없음 → 기존 캐시 즉시 반환 + 백그라운드 KIS 갱신
|
||||
③ 서버 재시작 등 캐시 비어있음 → DB 즉시 반환 + 백그라운드 KIS 갱신
|
||||
④ 매매 발생(change_flag) 또는 DB도 비어있음 → KIS API 동기 호출 (필수)
|
||||
"""
|
||||
latest_trade_id = await _get_latest_executed_trade_id(db)
|
||||
if latest_trade_id and portfolio_cache.last_trade_id != latest_trade_id:
|
||||
portfolio_cache.mark_changed()
|
||||
|
||||
# ① 신선한 캐시
|
||||
if portfolio_cache.holdings and portfolio_cache.summary and not portfolio_cache.should_sync():
|
||||
logger.debug("Portfolio cache hit (combined)")
|
||||
return _build_combined_resp()
|
||||
|
||||
# ② 캐시 만료, 매매 변경 없음 → stale 데이터 즉시 반환 + 백그라운드 갱신
|
||||
if portfolio_cache.holdings and portfolio_cache.summary and not portfolio_cache.change_flag:
|
||||
logger.debug("Portfolio cache stale (no trade) → returning cached, syncing in background")
|
||||
asyncio.create_task(_sync_portfolio_background())
|
||||
return _build_combined_resp()
|
||||
|
||||
# ③ 캐시 비어있음 (서버 재시작) → DB에서 즉시 로드 + 백그라운드 KIS 갱신
|
||||
if not portfolio_cache.holdings:
|
||||
result = await db.execute(select(Portfolio).where(Portfolio.quantity > 0))
|
||||
db_holdings = list(result.scalars().all())
|
||||
if db_holdings:
|
||||
portfolio_cache.holdings = db_holdings
|
||||
portfolio_cache.cache_summary() # available_cash=0이지만 holdings는 즉시 표시
|
||||
logger.info(f"Portfolio cold-start: DB {len(db_holdings)}종목 즉시 반환, KIS 갱신 백그라운드 시작")
|
||||
asyncio.create_task(_sync_portfolio_background())
|
||||
return _build_combined_resp()
|
||||
|
||||
# ④ 매매 발생 또는 DB도 비어있음 → KIS API 동기 호출
|
||||
try:
|
||||
await sync_portfolio(db, force=False)
|
||||
except HTTPException:
|
||||
logger.warning("get_portfolio_combined: sync failed, returning cached data")
|
||||
|
||||
return _build_combined_resp()
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def refresh_portfolio(db: AsyncSession = Depends(get_db)):
|
||||
"""강제 캐시 무효화 및 재동기화"""
|
||||
|
||||
Reference in New Issue
Block a user