┌─────────────────────────────────────────────────────────────┐
│ GIL과 Celery Pool의 관계 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GIL (Global Interpreter Lock): │
│ • 한 번에 하나의 스레드만 Python 바이트코드 실행 │
│ • CPU-bound 작업에서 멀티스레딩 효과 없음 │
│ • ⚠️ 단, I/O 대기 중에는 GIL이 해제됨 │
│ │
│ prefork Pool의 설계 의도: │
│ • 각 Worker가 독립 프로세스 → 독립 GIL │
│ • CPU-bound 작업의 진정한 병렬 처리 │
│ • Python GIL 우회 │
│ │
│ ⚠️ 문제: 이코에코 워크로드는 CPU-bound가 아님! │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ prefork Pool 구조적 한계 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 메모리 비효율 │
│ ───────────────── │
│ • 각 Worker가 독립 프로세스 │
│ • Python 인터프리터 복제 (수십~수백 MB/프로세스) │
│ • 24 workers × ~150MB = 3.6GB+ 메모리 필요 │
│ • t3.medium (4GB)에서 OOM 발생 │
│ │
│ 2. 동시성 한계 │
│ ───────────── │
│ • 1 Worker = 1 Task (동시 처리 불가) │
│ • I/O 대기 중에도 Worker 점유 │
│ • 실제 가용 Workers: 6-9개 (메모리 제한) │
│ • 최대 동시 처리: 6-9 requests │
│ │
│ 3. 확장 비용 │
│ ────────────── │
│ • Worker 추가 = 메모리 증가 │
│ • 100 동시 처리 = 100 프로세스 = 15GB+ 메모리 │
│ • 비현실적인 리소스 요구 │
│ │
└─────────────────────────────────────────────────────────────┘
7. 개선 방안
7.1 ⚠️ Celery는 AsyncIO Pool을 공식 지원하지 않음
┌─────────────────────────────────────────────────────────────┐
│ Celery AsyncIO Pool 지원 현황 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Celery 5.6.0 (2025-11-30, Latest): │
│ ──────────────────────────────────── │
│ • ❌ 네이티브 asyncio pool 미포함 │
│ • 공식 Pool: prefork, threads, gevent, eventlet │
│ │
│ 외부 패키지 시도 결과: │
│ ────────────────────── │
│ • celery-pool-asyncio → ❌ Celery 5.4+ 비호환, 업데이트 중단 │
│ (AttributeError: trace.monotonic) │
│ • celery-aio-pool → ❌ stars 75 커뮤니티 기반, 미검증 │
│ (Python 3.8-3.10만 지원) │
│ │
│ 결론: │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Celery에서 async def Task를 직접 사용할 수 없음 │ │
│ │ → Gevent Pool이 I/O-bound에 가장 적합한 대안 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
7.2 Gevent Pool 전환 (권장)
동시 I/O
6-9
100+
메모리
3.6GB+
~500MB
블로킹
100% (41초/req)
자동 yield
예상 RPS
0.22
OpenAI 한계(~4 RPS)까지
# 변경 전
args: [-P, prefork, -c, '8']
# 변경 후
args: [-P, gevent, -c, '100']
7.3 예상 개선 효과
RPS
0.0323
~4 RPS (OpenAI 한계)
RPM
1.94
~240 RPM
동시 처리
6-9
100+
메모리
3.6GB
~500MB
7.4 왜 Gevent가 해결책인가?
┌─────────────────────────────────────────────────────────────┐
│ Gevent가 I/O-bound에 최적인 이유 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Monkey Patching으로 자동 비동기화 │
│ ───────────────────────────────── │
│ • 표준 라이브러리의 블로킹 I/O를 자동 전환 │
│ • 기존 동기 코드 수정 없이 사용 가능 │
│ • socket, time.sleep, select 등 자동 패치 │
│ │
│ 2. 협력적 멀티태스킹 (Greenlet) │
│ ───────────────────────────── │
│ • I/O 대기 시 자동으로 다른 Greenlet으로 전환 │
│ • 단일 프로세스로 수천 동시 작업 처리 │
│ • 메모리 효율적 (Greenlet = 수 KB) │
│ │
│ 3. Celery 공식 지원 │
│ ──────────────────── │
│ • Celery 5.6.0에서 공식 지원되는 Pool │
│ • 안정성 검증됨 │
│ • 외부 패키지 의존 없음 │
│ │
│ 동작 예시: │
│ ┌────────────────────────────────────────────────────┐ │
│ │ @celery_app.task │ │
│ │ def vision_task(image_url): │ │
│ │ # 기존 동기 코드 그대로 사용 │ │
│ │ result = openai.vision(image_url) │ │
│ │ # ↑ gevent가 I/O 대기 시 자동 yield │ │
│ │ # 다른 Greenlet 실행 후 복귀 │ │
│ │ return result │ │
│ │ │ │
│ │ # 100개 Greenlet 동시 실행 │ │
│ │ # 메모리: ~500MB (prefork 대비 85% 절감) │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
8. 개선안
Gevent Pool 전환 → 100+ 동시 I/O
기존 동기 코드 유지 → Monkey Patching으로 자동 비동기화
성능 재측정 → Gevent 전환 후 비교
Gemini 병행 도입 검토 (RPM Limits)
9. 핵심 교훈
┌─────────────────────────────────────────────────────────────┐
│ 핵심 교훈 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 워크로드 유형 파악이 우선 │
│ ───────────────────────── │
│ • CPU-bound vs I/O-bound 명확히 구분 │
│ • 실측 데이터로 검증 (Prometheus) │
│ │
│ 2. prefork ≠ 만능 해결책 │
│ ───────────────────────── │
│ • CPU-bound에만 효과적 │
│ • I/O-bound에서는 오히려 비효율 │
│ • 메모리 오버헤드 고려 │
│ │
│ 3. Celery AsyncIO Pool 미지원 │
│ ────────────────────────── │
│ • Celery 5.6.0 (Latest)에서도 미포함 │
│ • 외부 패키지(celery-pool-asyncio, celery-aio-pool) 비호환 │
│ • Gevent가 I/O-bound에 가장 적합한 공식 대안 │
│ │
│ 4. 측정 → 분석 → 최적화 │
│ ──────────────────────── │
│ • 가정하지 말고 측정하라 │
│ • 병목이 어디인지 데이터로 확인 │
│ │
└─────────────────────────────────────────────────────────────┘