From 1e9432a6e3e1178a0df90e1c7be8e2010e49222c Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Sat, 14 Mar 2026 20:18:10 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20exhibit/bulk=EC=97=90=EC=84=9C=20concurr?= =?UTF-8?q?ent=20DB=20=EC=84=B8=EC=85=98=20=EC=B6=A9=EB=8F=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asyncio.gather로 동시에 실행되는 exhibit 항목들이 단일 DB 세션을 공유하면서 "concurrent operations are not permitted" 에러 발생. 각 _fetch_one 태스크가 AsyncSessionLocal()로 독립 세션 사용하도록 수정. Co-Authored-By: Claude Sonnet 4.6 --- app/api/v1/endpoints/filings.py | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/app/api/v1/endpoints/filings.py b/app/api/v1/endpoints/filings.py index 7c6372f..7fc37d0 100644 --- a/app/api/v1/endpoints/filings.py +++ b/app/api/v1/endpoints/filings.py @@ -10,7 +10,7 @@ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query, Response from sqlalchemy.ext.asyncio import AsyncSession -from app.core.database import get_db +from app.core.database import get_db, AsyncSessionLocal from app.schemas.filing import ( BulkExhibitItem, BulkExhibitRequest, @@ -290,27 +290,29 @@ async def get_exhibit_bulk( async def _fetch_one(item: dict) -> BulkExhibitItem: accession_number = item.get("accession_number", "") exhibit_type = item.get("exhibit_type", "EX-99.1") - try: - result = await asyncio.wait_for( - sec_filings_service.get_exhibit_content(db, accession_number, exhibit_type), - timeout=30, - ) - return BulkExhibitItem( - accession_number=accession_number, - exhibit_type=exhibit_type, - success=True, - content=result["content"], - content_type=result.get("content_type"), - filename=result.get("filename"), - url=result.get("url"), - ) - except Exception as e: - return BulkExhibitItem( - accession_number=accession_number, - exhibit_type=exhibit_type, - success=False, - error=str(e), - ) + # Each concurrent call gets its own DB session to avoid session contention + async with AsyncSessionLocal() as session: + try: + result = await asyncio.wait_for( + sec_filings_service.get_exhibit_content(session, accession_number, exhibit_type), + timeout=30, + ) + return BulkExhibitItem( + accession_number=accession_number, + exhibit_type=exhibit_type, + success=True, + content=result["content"], + content_type=result.get("content_type"), + filename=result.get("filename"), + url=result.get("url"), + ) + except Exception as e: + return BulkExhibitItem( + accession_number=accession_number, + exhibit_type=exhibit_type, + success=False, + error=str(e), + ) raw = await asyncio.gather(*[_fetch_one(item) for item in request.items], return_exceptions=True) results: List[BulkExhibitItem] = []