소프트웨어 개발/백엔드

📚[FastAPI] 7장. 비동기 작업 및 배포: Celery, Docker로 확장성 높이기

브라더댄 2025. 1. 26. 14:09
728x90
반응형
SMALL

안녕하세요! 이번 포스팅에서는 비동기 작업 처리와 배포 전략을 중점적으로 살펴보겠습니다. 현대적인 웹 애플리케이션은 단순 동기 REST API만으로는 부족할 때가 많습니다. 예를 들어 이미지 처리, 이메일 발송, 데이터 분석 등 시간이 오래 걸리는 작업은 비동기로 처리해야 서버의 응답성을 유지할 수 있습니다. 또한, 애플리케이션을 확장하기 위해선 Docker 컨테이너 기반으로 배포하는 전략이 필수에 가까워졌습니다.

이번 장에서는 Celery + Redis 조합을 통해 비동기 작업을 구현하고, DockerDocker Compose를 이용해 FastAPI 애플리케이션을 손쉽게 배포·관리하는 방법을 자세히 알아보겠습니다.


7.1. 비동기 작업의 필요성

7.1.1. 언제 비동기 처리가 필요한가?

  • 장시간 연산: 대용량 데이터 처리, AI 모델 예측, 영상 인코딩 등 한 번의 요청에 몇 초 이상 걸리는 작업
  • 네트워크 지연: 외부 API나 파일 업로드 같은 네트워크 의존 작업
  • 고객 경험 향상: REST API를 빠르게 응답시키고, 실제 처리는 백그라운드에서 진행해 사용자가 “대기”를 최소화

7.1.2. Python에서의 비동기 옵션

  1. Async/Await: FastAPI 자체가 비동기 I/O를 지원하지만, 대규모 백그라운드 작업은 추가적인 아키텍처가 필요
  2. Celery: 메시지 큐(예: Redis, RabbitMQ)와 연동해 분산 작업을 수행하는 대표적인 Python 프레임워크
  3. RQ, Huey 등 경량 작업 큐: 규모가 작거나 단순한 경우에 사용

이번 강의에서는 가장 많이 사용되는 Celery를 중점적으로 다룹니다.


7.2. Celery와 Redis를 활용한 비동기 처리

7.2.1. Celery 개요

  • Celery는 작업(Tasks)을 큐(Queue)에 넣고, 여러 워커(Worker)가 이를 소비(Consume)하여 처리하는 아키텍처를 제공합니다.
  • Broker(ex. Redis, RabbitMQ)가 작업 큐를 관리하고, 필요에 따라 여러 개의 워커 프로세스를 띄워 병렬 작업을 수행할 수 있습니다.
  • 성능, 확장성, 유연성 면에서 널리 검증된 솔루션이며, FastAPI와도 쉽게 연동할 수 있습니다.

7.2.2. Redis 설정

  • Redis는 메모리 기반 저장소로, Celery에서 가장 흔히 쓰이는 브로커 중 하나입니다.
  • 설치 후, redis:// 형태의 URL로 접근할 수 있습니다. (예: redis://localhost:6379/0)

7.2.3. Poetry로 Celery & Redis 설치

poetry add celery redis

TIP: 필요하다면 Celery 플러그인을 추가(celery[s3], celery[redis] 등)하여 기능을 확장할 수 있습니다.

7.2.4. Celery 설정 (celery_app.py 예시)

# app/celery_app.py
import os
from celery import Celery

REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0")

celery_app = Celery(
    "worker",
    broker=REDIS_URL,
    backend=REDIS_URL,  # 결과 저장소도 Redis 사용
)

celery_app.conf.update(
    result_expires=3600,  # 작업 결과 만료 시간 (예시)
    task_serializer="json",
    accept_content=["json"],
    result_serializer="json",
    timezone="Asia/Seoul",
    enable_utc=True,
)
  • broker와 backend를 동일하게 REDIS_URL로 설정하면, 작업 큐와 결과를 모두 Redis에서 관리
  • celery_app 인스턴스 하나를 전역적으로 생성해 필요한 모듈에서 가져다 씁니다.

7.2.5. 작업 정의 및 호출

# app/tasks.py
from app.celery_app import celery_app
import time

@celery_app.task
def long_running_task(param: int) -> str:
    # 예: 10초간 대기 후 결과 반환
    time.sleep(10)
    return f"Processed {param} successfully!"
  • @celery_app.task 데코레이터를 사용해 함수를 Celery 작업으로 등록
  • 실행은 다음과 같이 진행됩니다:
    from app.tasks import long_running_task
    result = long_running_task.delay(42)  # 즉시 반환, 비동기로 처리
    # result.get() 을 호출하면 blocking 모드로 실제 결과를 기다릴 수 있음
    

7.2.6. Celery 워커 실행

celery -A app.celery_app.celery_app worker --loglevel=info
  • -A 옵션으로 Celery 앱(여기서는 celery_app)을 지정
  • 워커는 별도 프로세스로 실행되며, Redis에 들어오는 작업을 계속 모니터링합니다.

7.3. FastAPI와 Celery 연동 예시

7.3.1. API에서 비동기 작업 트리거

# app/api/v1/endpoints/long_task.py
from fastapi import APIRouter
from app.tasks import long_running_task

router = APIRouter()

@router.post("/start-task")
def start_long_task(param: int):
    task_result = long_running_task.delay(param)
    return {"task_id": task_result.id}
  • 사용자가 /start-task 엔드포인트를 호출하면, Celery 작업이 큐에 들어가고 즉시 응답
  • 비동기로 작업이 처리되는 동안 API 서버는 다른 요청을 처리할 수 있습니다.

7.3.2. 작업 상태 조회

@router.get("/task-status/{task_id}")
def get_task_status(task_id: str):
    from app.celery_app import celery_app
    task_result = celery_app.AsyncResult(task_id)
    return {
        "task_id": task_id,
        "status": task_result.status,
        "result": task_result.result if task_result.ready() else None
    }
  • Celery는 작업의 상태(PENDING, STARTED, SUCCESS, FAILURE 등)와 결과를 추적 가능
  • 클라이언트는 주기적으로 이 엔드포인트를 조회하거나, 이벤트(웹소켓 등)를 통해 실시간 상태 업데이트를 받을 수 있습니다.

7.4. Docker를 활용한 배포

7.4.1. Docker의 장점

  • 환경 일관성: 모든 종속 라이브러리와 실행 환경을 컨테이너 이미지에 명시하므로, “내 컴퓨터에서만 돌아가는” 문제를 방지합니다.
  • 확장성: 동일 이미지를 여러 서버(혹은 컨테이너 오케스트레이션 툴)에서 실행해 트래픽을 쉽게 분산할 수 있습니다.
  • 표준화: DevOps 파이프라인(CI/CD)에서 Docker는 사실상 표준으로 자리잡았습니다.

7.4.2. Dockerfile 작성 예시

# Dockerfile
FROM python:3.10-slim

WORKDIR /app

# Poetry 설치
RUN pip install --upgrade pip && pip install poetry

# pyproject.toml, poetry.lock 복사
COPY pyproject.toml poetry.lock /app/

# 라이브러리 설치
RUN poetry install --no-dev --no-interaction --no-ansi

# 소스 코드 복사
COPY . /app

# FastAPI 실행 (개발용: --reload 제외, 실제 배포는 --reload 빼는 게 일반적)
CMD ["poetry", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
  • Python 공식 이미지(python:3.10-slim)를 베이스로 사용
  • Poetry를 설치하고, pyproject.toml 기반으로 라이브러리를 설치
  • uvicorn으로 FastAPI 앱을 기동

TIP: 프로덕션 환경용 Dockerfile은 캐싱 전략, 멀티 스테이지 빌드 등을 적용해 더 최적화할 수 있습니다.

7.4.3. Docker Compose 예시

# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    container_name: fastapi-app
    ports:
      - "80:80"
    depends_on:
      - redis

  worker:
    build: .
    container_name: celery-worker
    command: poetry run celery -A app.celery_app.celery_app worker --loglevel=info
    depends_on:
      - redis

  redis:
    image: redis:6.2
    container_name: redis-broker
    ports:
      - "6379:6379"
  • web: FastAPI 앱 컨테이너
  • worker: Celery 워커 컨테이너
  • redis: Redis 컨테이너 (브로커 및 결과 저장소)
  • docker-compose up --build로 모든 서비스를 동시에 빌드 & 실행

7.5. 확장 시나리오: AWS, GCP 배포

7.5.1. AWS ECS / EKS

  • AWS ECS(Elastic Container Service) 또는 AWS EKS(Elastic Kubernetes Service)를 사용해 Docker 이미지를 클러스터에 배포
  • AWS Fargate를 통해 서버리스 방식으로 컨테이너를 동적으로 스케일링 가능
  • Elastic Load Balancer(ALB) + Auto Scaling 정책을 설정해 트래픽 증가에 대응

7.5.2. GCP Cloud Run / GKE

  • Google Cloud Run: 컨테이너 이미지만 업로드하면, 서버리스 방식으로 HTTP 엔드포인트 제공
  • GKE(Google Kubernetes Engine)는 Kubernetes 오케스트레이션 환경을 제공, 대규모 트래픽을 다루는 데 유리
  • 각각의 파드(Pod)나 인스턴스에 Celery 워커를 띄워 분산 처리

7.5.3. CI/CD 연동

  • GitHub Actions, GitLab CI, Jenkins 등으로 Docker 빌드 → 테스트 → 푸시 → 배포 자동화
  • Infra as Code(Terraform, AWS CDK, Pulumi)로 인프라 설정을 버전 관리하면 안정성과 재현성을 극대화

7.6. 실제 활용 사례

7.6.1. 이미지/영상 처리 서비스

  • 업로드된 파일은 Celery 비동기 작업으로 넘겨 썸네일 생성 또는 영상 인코딩 작업을 처리
  • UI는 즉시 업로드 성공 응답을 받고, 배경에서 변환이 완료되면 별도 알림(이메일, Push, WebSocket 등)을 전송
  • Docker Compose 또는 Kubernetes 환경에서 워커 노드를 확장해 대규모 파일 처리를 분산

7.6.2. 대규모 로그 분석

  • 요청마다 로그를 DB나 메시지 큐에 저장한 뒤, Celery 작업에서 실시간 분석 또는 통계 집계 수행
  • Redis 대신 RabbitMQSQS 같은 다른 브로커로 교체할 수도 있음
  • Docker 기반으로 워커들을 스케일 아웃하여 처리량을 늘릴 수 있음

7.6.3. 뉴스레터 발송 시스템

  • 여러 만 명에게 동시에 이메일을 발송해야 하는 경우, 동기 방식은 타임아웃/지연 발생
  • Celery 워커가 이메일 발송 작업을 병렬로 처리해 빠른 대량 발송 가능
  • Docker Swarm이나 Kubernetes로 배포해 배포 자동화 및 스케일링 관리

모범 사례

  1. 비동기 작업과 DB 트랜잭션 분리: 요청-응답 트랜잭션에서 무거운 로직은 Celery로 넘기고, API는 빠르게 응답
  2. Idempotent 작업: 비동기 작업은 재시도(Retry) 시에도 중복 문제가 없어야 함
  3. 모니터링: Celery 작업 대기열, 성공/실패 건수를 Grafana, Prometheus 등으로 확인
  4. Docker 멀티 스테이지 빌드: 빌드 속도 단축 및 이미지 크기 최적화
  5. 시크릿 관리: API 키, DB 비밀번호 등 민감 정보는 .env 파일 또는 Docker Secret, Vault 등을 사용해 안전하게 보관

7.7. 마무리

이번 7장에서는 비동기 작업(Celery)과 Docker 기반 배포에 대해 알아보았습니다.

  1. 비동기 작업이 필요한 이유와 Celery + Redis를 사용해 장시간 작업을 백그라운드에서 처리하는 방법
  2. Dockerfile, Docker Compose를 통해 FastAPI 앱, Celery 워커, Redis를 한꺼번에 컨테이너로 띄우고 관리하는 과정
  3. AWS나 GCP와 같은 클라우드 환경에서 확장성을 극대화하는 방법과 CI/CD 연동 개념

다음 8장에서는 예시로 종합 프로젝트 실습(간단한 블로그/게시판 API 등)을 통해 여기까지 배운 내용들을 실제로 융합하여 구현해볼 예정입니다. 비동기 작업, 인증, 배포 전략까지 종합해보면, 프로덕션급 백엔드 시스템 구축 흐름을 한 번에 이해할 수 있을 것입니다. 부디 이번 장의 내용이 실무 개발과 DevOps 업무에 도움이 되길 바랍니다!

 

728x90
반응형
LIST