[FastAPI] Gunicorn threads 옵션
2024. 11. 6. 15:23
CMD="gunicorn \
-w $WORKERS \
-k uvicorn.workers.UvicornWorker \
-b 0.0.0.0:$PORT \
--timeout $TIMEOUT_SECONDS \
--threads $THREADS \
callisto.server:app"
threads 옵션을 사용하는 경우 (-w 2, --threads 4)
[Worker 1 (프로세스)]
├── Thread 1: FastAPI 이벤트 루프 (비동기 요청 처리) <-- 항상 1개!
├── Thread 2: FastAPI 동기 요청 처리 (스레드 풀)
├── Thread 3: FastAPI 동기 요청 처리 (스레드 풀)
├── Thread 4: FastAPI 동기 요청 처리 (스레드 풀)
└── (추가적인 백그라운드 스레드, 예: threading.Thread())
[Worker 2 (프로세스)]
├── Thread 1: FastAPI 이벤트 루프 (비동기 요청 처리) <-- 항상 1개!
├── Thread 2: FastAPI 동기 요청 처리 (스레드 풀)
├── Thread 3: FastAPI 동기 요청 처리 (스레드 풀)
├── Thread 4: FastAPI 동기 요청 처리 (스레드 풀)
└── (추가적인 백그라운드 스레드, 예: threading.Thread())
실제 동작 예시
# 비동기 엔드포인트 - 이벤트 루프에서 처리
@app.get("/async")
async def async_endpoint():
await some_io_operation() # 이벤트 루프에서 처리
# 동기 엔드포인트 - 스레드 풀에서 처리
@app.get("/sync")
def sync_endpoint():
time.sleep(1) # 스레드 풀의 스레드에서 처리
threads 옵션은 주로 동기 작업을 처리하기 위한 것
비동기 작업은 여전히 단일 스레드의 이벤트 루프에서 처리
FastAPI에서 async로 정의된 엔드포인트는 threads 옵션과 관계없이 비동기로 처리
1. GIL과 I/O 바운드 작업의 관계
GIL이 있더라도 I/O 바운드 작업에서는 멀티스레딩이 효과적
def sync_endpoint():
# I/O 작업 (예: DB 쿼리, 파일 읽기, HTTP 요청)
time.sleep(1) # I/O 작업을 시뮬레이션
return "done"
- Thread A가 I/O 작업을 시작하며 GIL을 해제
- 그 동안 Thread B가 GIL을 획득하여 다른 I/O 작업 수행
2. Gunicorn threads 옵션
# 시나리오 1: I/O 바운드 동기 작업
@app.get("/sync")
def sync_endpoint():
time.sleep(1) # I/O 작업
return {"result": "done"}
# 시나리오 2: CPU 바운드 동기 작업
@app.get("/cpu")
def cpu_endpoint():
result = 0
for i in range(1000000): # CPU 집중 작업
result += i
return {"result": result}
- I/O 바운드 작업(시나리오1) : threads 옵션이 효과적
- CPI 바운드 작업(시나리오2): threads 옵션이 거의 효과 없음
3. async vs threads 선택 기준
# 비동기 방식
@app.get("/async")
async def async_endpoint():
await asyncio.sleep(1)
return {"result": "done"}
# 스레드 방식
@app.get("/threaded")
def threaded_endpoint():
time.sleep(1)
return {"result": "done"}
- 이미 async/await로 작성된 라이브러리 사용시
: async 엔드포인트
- 동기식 라이브러리만 있는 경우
: thread 사용
4. 권장 설정 방법
# 권장 설정
gunicorn -w 2 -k uvicorn.workers.UvicornWorker --threads 4 app:app
- async 엔드포인트 : 이벤트 루프에서 효율적으로 처리
- 동기 엔드포인트 : 스레드 풀에서 처리
- 적절한 스레드 수 : CPU 코어 수 * 2~4
* async가 있더라도 threads 옵션을 1로 두지 않는게 좋음
: 동기 코드로 처리해야 할 수 있기 때문
* 무작정 threads 옵션을 늘리는 것은 좋지 않음
: 컨텍스트 스위칭 오버헤드 증가
* 실제 워크로드에 따라 적절한 threads 설정 필요
'WebFramework > [FastAPI]' 카테고리의 다른 글
[FastAPI] SocketIO Admin UI (0) | 2024.01.25 |
---|---|
[FastAPI] room 기능 구현 및 worker간 공유 (RabbitMQ) (0) | 2024.01.24 |
[FastAPI] SocketIO 마운트해서 사용하기 (0) | 2024.01.24 |
[FastAPI] BackgroundTasks, Celery (0) | 2024.01.09 |
[FastAPI] 중첩된 JSON 모델(Nested JSON Models) 사용 (0) | 2023.09.25 |