최근 작업에서는 텍스트를 전처리한 후, GPU 서버에 있는 LLM 모델을 호출해 분석하는 태스크를 처리하고 있다. 그런데 이 과정에서 동시에 여러 개의 요청이 들어오면 500 에러가 발생하는 문제가 있었다.
서버 리소스를 보호하기 위해 asyncio.Semaphore를 적용해 작업의 동시 실행 수를 제한했다. 이 방법 덕분에 한 번에 너무 많은 작업이 실행되는 것을 막을 수 있었고, 즉각적인 메모리 과부화(OOM)는 방지할 수 있었다.
하지만 요청이 많아지면 전체 처리 속도가 느려졌고, 결국 Semaphore는 "한 호출 내의 동시성"을 제한할 뿐, 여러 사용자의 요청이 동시에 들어오는 상황 자체는 여전히 컨트롤할 수 없었다.
이러한 구조의 한계 때문에 자연스럽게 Celery라는 도구를 검토하게 되었다.
Celery는 Python에서 널리 사용되는 비동기 작업 큐(Task Queue) 시스템이다.
웹 서버로 들어온 무거운 작업을 큐에 넣어, 웹 프로세스와는 분리된 워커(worker)가 비동기적으로 실행하도록 해준다. 이 과정에서 Redis, RabbitMQ 같은 메시지 브로커(Broker)를 통해 작업 요청과 실행을 안전하게 분리한다.
다음과 같은 작업은 서버에서 직접 처리하면 무겁고, 응답 지연 또는 서버 과부하를 초래할 수 있다:
문제 | 설명 |
---|---|
느린 응답 | 작업 완료까지 대기해야 하므로 사용자 경험 저하 |
서버 과부하 | CPU, 메모리 과도하게 사용됨 |
장애 가능성 | 동시에 여러 요청이 몰릴 경우 OOM 발생 |
확장 어려움 | 단일 서버나 프로세스 수준에서 동시성 제어 한계 |
*이 구조를 따르면 요청과 실행이 분리되고, 작업은 워커가 필요한 만큼만 꺼내서 처리하므로 동시 처리량 제한이나 분산 처리도 쉽게 가능해진다.
[ FastAPI 서버 ] → [ Redis (Broker) ] → [ Celery 워커 ] → [ 결과 저장 (선택) ]
# celery_app.py
from celery import Celery
celery_app = Celery(
"worker",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1"
)
# tasks.py
from celery_app import celery_app
@celery_app.task
def slow_add(x, y):
import time
time.sleep(5)
return x + y
# main.py
from fastapi import FastAPI
from tasks import slow_add
app = FastAPI()
@app.get("/add")
def add(x: int, y: int):
task = slow_add.delay(x, y)
return {"task_id": task.id, "status": "processing"}
Celery를 도입하면 복잡하거나 무거운 작업을 비동기적으로 분리할 수 있어 웹 서버의 안정성과 응답 속도를 개선할 수 있다. 단순한 asyncio.Semaphore 기반의 동시성 제어가 구조적으로 한계를 가지는 상황이라면, Celery는 훨씬 확장 가능한 대안이 될 수 있다.