Python 비동기 처리에서 발생한 Event Loop 블로킹 해결

문제 상황

FastAPI로 구축한 이미지 처리 API에서 간헐적으로 응답 지연이 발생했다. 모니터링 결과 특정 엔드포인트 호출 시 다른 요청들까지 수 초간 대기하는 현상이었다.

@app.post("/process-image")
async def process_image(file: UploadFile):
    image_data = await file.read()
    result = heavy_image_processing(image_data)  # 문제 지점
    return {"result": result}

원인 분석

heavy_image_processing 함수가 CPU 집약적 동기 함수였다. async 함수 내에서 동기 함수를 직접 호출하면 event loop가 블로킹되어 다른 코루틴들이 실행되지 못한다.

해결 방법

run_in_executor를 사용해 별도 스레드 풀에서 처리하도록 변경했다.

import asyncio
from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=4)

@app.post("/process-image")
async def process_image(file: UploadFile):
    image_data = await file.read()
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        executor, 
        heavy_image_processing, 
        image_data
    )
    return {"result": result}

CPU 집약적 작업의 경우 ProcessPoolExecutor도 고려할 수 있지만, 이미지 데이터 직렬화 오버헤드를 고려해 ThreadPoolExecutor를 선택했다.

결과

동시 요청 처리 시 응답 시간이 안정화되었다. Python의 비동기 처리는 I/O bound 작업에 효과적이지만, CPU bound 작업은 별도 처리가 필요하다는 점을 다시 확인했다.