제목, 태그, 카테고리로 검색

모든 글
약 6분 분량 프로젝트/오락가락

[트러블슈팅] AI 서버 동시 요청 시 GPU OOM

목차

한 줄 요약

동시 요청 제한 없이 AI 서버에 보내다가 하루 5-10회 GPU OOM이 터졌어요.
ThreadPool(max=4) + Semaphore(permits=2)로 이중 동시성 제어를 걸어서 OOM 0회로 잡았어요.


증상

운영 중에 음성 분석 요청이 몰리면 Python AI 서버가 죽었어요.
Pod가 OOMKilled 상태로 재시작되고, 재시작되는 동안 모든 분석 요청이 타임아웃.
하루에 5-10회 반복됐어요.
Grafana의 컨테이너 모니터링 대시보드에서 AI 서버 Pod의 재시작 횟수가 하루 단위로 올라가는 걸 확인했어요.

환경

  • Python FastAPI (AI 서버), PyTorch + GPU (8GB VRAM)
  • Spring Boot (API 서버), WebClient 비동기 호출
  • Docker Compose 단일 서버 구성

원인 분석

GPU 메모리를 계산해봤어요.

GPU 전체 메모리 8GB 중 모델 로딩에 약 3GB가 상시 점유돼요.
추론 1건당 약 2-3GB를 써요.
최대 동시 처리는 (8 - 3) / 2.5 = 약 2건이에요.

그런데 Kafka Consumer에서 이벤트가 들어오는 대로 AI 서버에 요청을 쏘고 있었어요.
동시에 5건만 들어오면 필요 메모리가 VRAM을 초과하니 OOM이 나는 게 당연했어요.

AI 분석 담당 팀원과 같이 nvidia-smi로 GPU 메모리 사용량을 모니터링하면서 동시 요청 수와 OOM 발생 시점을 대조했어요.
동시 3건까지는 안정적이고, 4건부터 가끔 스파이크가 나고, 5건 이상이면 거의 확실하게 OOM이 터졌어요.


해결: ThreadPool + Semaphore 이중 제어

단순히 동시 요청을 줄이면 되는 문제가 아니었어요.
WAV 변환과 AI 분석이 같은 Consumer에서 처리되는데, WAV 변환은 CPU 바운드라 빠르게 끝나고 AI 분석은 GPU를 오래 점유하거든요.
같은 스레드풀로 처리하면 AI 분석이 WAV 변환까지 블로킹하거든요.

그래서 두 가지를 분리했어요.

ThreadPool(max=4): 시스템 내부 리소스(CPU, 메모리) 보호.
최대 4개 스레드까지 작업을 처리해요.

Semaphore(permits=2): 외부 서비스(AI 서버 GPU) 보호.
4개 스레드가 동시에 실행되더라도 AI 서버에는 2개만 동시 요청해요.

permits를 2로 잡은 이유는, GPU 계산상 최대 동시 처리가 약 2건이고, 요청마다 메모리 사용량이 다르기 때문에(음성 길이, 복잡도에 따라 편차) 안전 마진을 포함한 수치예요.

검토했지만 선택하지 않은 대안

방식장점단점판단
Kafka max.poll.records 조절설정만 변경Consumer 레벨 제한이라 GPU 메모리와 직접 연동 안 됨. WAV 변환까지 같이 제한됨탈락
Resilience4j RateLimiter시간 기반 요청률 제한AI 분석은 시간당 N건이 아니라 동시 N건이 문제. 처리 시간이 가변적이라 rate 기반은 부적합탈락
ThreadPool만 (Semaphore 없이)단순WAV 변환과 AI 분석이 같은 풀이면 AI가 WAV를 블로킹. 풀을 분리해도 AI 전용 풀 max=2로 하면 Semaphore와 사실상 동일부분 채택
ThreadPool + Semaphore내부(CPU) + 외부(GPU) 자원을 독립 제어구현 약간 복잡선택

ThreadPool만으로도 가능하지만, Semaphore로 “외부 GPU 자원에 대한 동시 접근 제한”을 명시적으로 분리한 게 Bulkhead 패턴의 의도를 더 잘 표현해요.

Semaphore Bean 설정

ThreadPool 설정

Semaphore 사용 코드


작업별 리소스 분리

작업 타입ThreadPoolSemaphore이유
WAV 변환5~108CPU 바운드, 빠른 처리
음성 분석2~42GPU 사용, 무거운 AI 처리
이미지 처리3~64중간 수준
배치 복구2~43백그라운드 처리

무거운 작업(AI 분석)이 가벼운 작업(WAV 변환)을 블로킹하지 않도록 풀을 분리한 게 핵심이에요.


결과

지표개선 전개선 후
AI 서버 OOM 발생하루 5-10회0회
평균 분석 대기 시간실패로 무한 대기30초
GPU 활용률불안정 (100% 스파이크)85% 안정
분석 성공률~70%99%+

요청이 폭주해도 세마포어 대기열에서 순차 처리되니 Pod 재시작이 0회가 됐어요.


참고 자료

Summary

Eliminated daily 5-10 GPU OOM crashes by implementing dual concurrency control: ThreadPool (max=4) for system resources and Semaphore (permits=2) for GPU protection, achieving zero OOM incidents.


Symptoms

During operation, the Python AI server crashed when voice analysis requests spiked. Pods restarted as OOMKilled, causing all analysis requests to timeout during restart. This repeated 5-10 times daily. Grafana’s container monitoring dashboard showed the AI server Pod restart count climbing daily.

Environment

  • Python FastAPI (AI server), PyTorch + GPU (8GB VRAM)
  • Spring Boot (API server), WebClient async calls
  • Docker Compose single-server setup

Root Cause

GPU memory calculation: 8GB total, ~3GB constant for model loading, ~2-3GB per inference. Maximum concurrent processing: (8 - 3) / 2.5 ≈ 2.

But the Kafka Consumer was firing requests to the AI server as fast as events arrived. 5 concurrent requests exceed available VRAM — OOM was inevitable.

Monitoring GPU memory with nvidia-smi alongside the AI team, we correlated concurrent request counts with OOM timing. 3 concurrent was stable, 4 showed occasional spikes, 5+ almost guaranteed OOM.


Solution: ThreadPool + Semaphore Dual Control

Simply reducing concurrent requests wasn’t enough. WAV conversion and AI analysis share the same Consumer, but WAV conversion is CPU-bound (fast) while AI analysis occupies the GPU long. A shared thread pool lets AI analysis block WAV conversion.

Two mechanisms were separated:

ThreadPool (max=4): Protects internal system resources (CPU, memory). Up to 4 threads process tasks.

Semaphore (permits=2): Protects external service (AI server GPU). Even with 4 threads running, only 2 can simultaneously request the AI server.

Permits were set to 2, matching the GPU’s theoretical max concurrent capacity of ~2, with per-request memory varying by audio length and complexity.

Semaphore Bean Config

ThreadPool Config

Semaphore Usage Code


Per-Task Resource Isolation

Task TypeThreadPoolSemaphoreReason
WAV Conversion5~108CPU-bound, fast
Voice Analysis2~42GPU-heavy AI processing
Image Processing3~64Medium workload
Batch Recovery2~43Background processing

The key is separating pools so heavy tasks (AI analysis) don’t block light tasks (WAV conversion).


Results

MetricBeforeAfter
AI server OOM5-10/day0
Avg analysis waitInfinite (failures)30s
GPU utilizationUnstable (100% spikes)85% stable
Analysis success rate~70%99%+

Even under request surges, sequential processing via semaphore queue reduced Pod restarts to zero.


References

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.