Published on

SageMaker 엔드포인트 502·타임아웃 해결 7단계

Authors

SageMaker 실시간 엔드포인트를 운영하다 보면 특정 시점부터 502(Bad Gateway)나 504(Gateway Timeout), 혹은 클라이언트 타임아웃이 간헐적으로 발생합니다. 문제는 “모델이 느려서”로 뭉뚱그리기 쉽지만, 실제로는 로드밸런서 레벨의 타임아웃, 컨테이너 워커/스레드 모델, 페이로드 크기, 인스턴스 자원 병목(CPU/GPU/메모리), 콜드 스타트, 오토스케일링 지연 등 원인이 겹쳐서 나타나는 경우가 많습니다.

아래 7단계는 “지표로 원인을 좁히고, 재현 가능한 개선을 적용하는” 순서로 구성했습니다. 각 단계는 독립적이지만, 보통 1에서 7까지 순서대로 진행하면 원인 분리가 훨씬 빨라집니다.

1) 현상 정의: 어떤 타임아웃이 어디서 터지는가

먼저 타임아웃/502가 클라이언트, API Gateway/ALB, SageMaker 런타임, 컨테이너 내부 중 어디에서 발생하는지 분리해야 합니다.

  • 클라이언트 SDK에서 ReadTimeoutError가 난다: 네트워크/응답 지연 또는 서비스 타임아웃
  • 응답이 502로 온다: 대개 요청은 도착했지만 백엔드가 정상 응답을 못함(프로세스 크래시, 워커 부족, 내부 예외 등)
  • 504로 온다: 상위 레이어에서 정해진 시간 내 응답이 없어서 끊김

Python boto3 호출에서 최소한 아래 정보는 로그로 남기세요.

import json
import time
import boto3
from botocore.config import Config

smr = boto3.client(
    "sagemaker-runtime",
    config=Config(
        read_timeout=70,   # 클라이언트 타임아웃 (원인 규명용으로 명시)
        connect_timeout=5,
        retries={"max_attempts": 0},
    ),
)

def invoke(endpoint_name: str, payload: dict):
    t0 = time.time()
    body = json.dumps(payload).encode("utf-8")
    try:
        resp = smr.invoke_endpoint(
            EndpointName=endpoint_name,
            ContentType="application/json",
            Body=body,
        )
        dt = (time.time() - t0) * 1000
        return dt, resp["Body"].read()
    except Exception as e:
        dt = (time.time() - t0) * 1000
        raise RuntimeError(f"invoke failed in {dt:.1f}ms: {e}")

이 단계의 목표는 “대충 느리다”가 아니라 P50/P95/P99 지연, 실패 비율, 실패의 형태(502 vs 타임아웃) 를 숫자로 고정하는 것입니다.

2) CloudWatch 지표로 병목 지점을 10분 안에 좁히기

SageMaker 엔드포인트는 CloudWatch에 핵심 지표를 올립니다. 502/타임아웃이라면 아래를 우선 봅니다.

  • ModelLatency: 모델 컨테이너가 요청을 처리한 시간(대개 inference 시간)
  • OverheadLatency: 라우팅/직렬화 등 오버헤드
  • Invocation4XXErrors, Invocation5XXErrors: 오류율
  • CPUUtilization, MemoryUtilization(일부 구성), GPU 사용률은 별도 에이전트/드라이버 구성 필요
  • InvocationsPerInstance: 인스턴스당 요청 수(동시성 압박 확인)

판단 기준 예시:

  • ModelLatency가 급증한다: 모델 추론 자체가 느리거나 배치/토크나이저/전처리 병목
  • OverheadLatency가 비정상적으로 크다: 페이로드가 크거나 직렬화/압축/네트워크 오버헤드
  • 5XX가 늘면서 ModelLatency는 낮다: 컨테이너 크래시, 워커 부족, 예외 처리 누락 가능성

운영 대시보드가 없다면 일단 AWS/ SageMaker 네임스페이스에서 위 지표를 한 화면에 모으세요. “지표로 진단”의 감각은 다른 장애 대응에도 그대로 적용됩니다. 예를 들어 재시도·백오프 설계는 OpenAI 429·rate_limit 재시도·백오프 설계 가이드처럼 실패 형태를 먼저 분류하는 것이 핵심입니다.

3) 컨테이너 로그에서 502의 진짜 원인 찾기(예외, 워커, 크래시)

502는 컨테이너가 요청을 제대로 처리하지 못했을 때 흔히 보입니다. CloudWatch Logs에서 엔드포인트 로그 그룹을 확인하고, 아래 패턴을 찾으세요.

  • OutOfMemoryError, CUDA out of memory, Killed(OOM Killer)
  • Python 예외 스택트레이스(입력 파싱 실패, 키 누락, 타입 오류)
  • 워커 타임아웃(Gunicorn/uvicorn worker timeout)
  • 모델 로딩 실패(아티팩트 경로, 권한, 디스크 용량)

특히 Hugging Face 기반 컨테이너나 커스텀 FastAPI를 올린 경우, “서버 워커 수”가 동시성을 좌우합니다. 워커가 1개인데 모델 추론이 10초면, 동시 요청이 들어오는 순간 대기열이 길어지고 상위 타임아웃으로 이어집니다.

Gunicorn을 쓴다면 엔트리포인트에서 워커/타임아웃을 명시하세요.

# 예: CPU 추론 서버
exec gunicorn app:app \
  --workers 2 \
  --threads 4 \
  --timeout 120 \
  --bind 0.0.0.0:8080

주의할 점은 워커를 무작정 늘리면 메모리 사용량이 워커 수만큼 증가할 수 있다는 것입니다(모델을 프로세스별로 로드하면 특히). 이 경우는 5단계의 “메모리/모델 최적화”로 같이 접근해야 합니다.

4) 엔드포인트 타임아웃/동시성 한계를 구조적으로 이해하기

SageMaker 실시간 추론은 다음 흐름으로 생각하면 디버깅이 쉬워집니다.

  1. 요청이 엔드포인트로 들어옴
  2. 라우팅 및 직렬화 오버헤드가 발생(OverheadLatency)
  3. 컨테이너의 웹서버 워커가 요청을 수신
  4. 모델 로딩/전처리/추론/후처리 수행(ModelLatency)
  5. 응답 반환

여기서 타임아웃은 여러 층에 존재합니다.

  • 클라이언트 타임아웃: SDK의 read_timeout
  • 웹서버 워커 타임아웃: Gunicorn --timeout
  • 상위 라우팅 레이어 타임아웃: 인프라 레벨 제한(구성에 따라 다름)

해결 전략은 “가장 바깥 타임아웃을 늘린다”가 아니라, P99를 줄이거나 동시성을 올려 큐잉을 줄이는 것입니다. 타임아웃을 늘리는 건 임시 완화책일 뿐이고, 비용과 장애 반경을 키울 수 있습니다.

5) 모델/전처리 최적화로 ModelLatency와 OOM을 동시에 줄이기

지표에서 ModelLatency가 높거나 로그에 OOM이 보이면, 모델/전처리 최적화가 우선입니다.

(1) 페이로드 줄이기

  • 입력 텍스트 길이 제한, 이미지 리사이즈
  • 불필요한 필드 제거
  • JSON 대신 바이너리(가능한 경우)

(2) 배치 추론을 “실시간에서 억지로” 하지 않기

실시간 엔드포인트에서 큰 배치를 처리하면 단일 요청이 오래 잡아먹어 동시성이 급락합니다. 배치가 필요하면 Batch Transform 또는 비동기 추론을 고려하세요.

(3) GPU OOM 완화

LLM/Transformers 계열이라면 OOM은 502/타임아웃의 주요 원인입니다. 아래 글의 체크리스트가 실전에 매우 유용합니다.

대표적으로는 mixed precision, max_new_tokens 제한, KV 캐시 전략, 배치 크기 제한, 모델 로딩 옵션(예: torch_dtype) 등이 있습니다.

(4) 토크나이저/전처리 병목 제거

CPU에서 토크나이징이 병목이면 GPU가 놀고 ModelLatency가 치솟습니다. 다음을 점검하세요.

  • 토크나이저를 요청마다 새로 생성하지 않는지
  • 병렬화가 오히려 느려지는지(락/분할 비용)

병렬화가 만능이 아닌 것처럼, 토크나이저 병렬 처리도 데이터 분할/병합 비용이 커지면 역효과가 납니다. 병렬화 진단 관점은 Java Stream 병렬이 느린 이유 - Spliterator·Collector 진단과 유사합니다.

6) 오토스케일링과 프로비저닝: “느린 확장”이 타임아웃을 만든다

트래픽이 갑자기 증가할 때만 502/타임아웃이 난다면, 모델이 느린 게 아니라 스케일 아웃이 늦어서 큐가 쌓이는 상황일 수 있습니다.

점검 포인트:

  • 최소 인스턴스 수가 1이고 콜드 스타트가 긴 모델인가
  • TargetTracking 정책의 목표값이 너무 공격적인가(예: InvocationsPerInstance를 너무 높게 잡음)
  • 스케일 아웃 쿨다운이 길어 급증 트래픽을 못 따라감

실전 팁:

  • P99가 중요하면 최소 인스턴스 수를 2 이상으로 두고 롤링 업데이트/장애 내성을 확보
  • 지표 기반 스케일링에서 “증가 속도”를 보수적으로(빠르게 늘고, 천천히 줄이기)
  • 트래픽 패턴이 예측 가능하면 스케줄 기반 스케일링도 고려

또한 이미지 빌드가 느려 배포가 늦고, 그 사이에 이전 버전이 과부하로 502가 나는 경우도 있습니다. CI에서 Docker 빌드 시간을 줄이면 롤백/재배포가 빨라져 장애 시간을 줄일 수 있습니다.

7) 재현 가능한 부하 테스트와 “실패를 안전하게” 만드는 클라이언트 전략

마지막 단계는 개선이 실제로 효과가 있는지 검증하고, 남는 실패를 클라이언트에서 안전하게 흡수하는 것입니다.

(1) 부하 테스트로 P95/P99와 오류율을 고정

간단하게는 동시 요청을 늘리며 ModelLatency5XX가 언제부터 증가하는지 확인합니다.

import asyncio
import json
import time
import aiohttp

ENDPOINT_URL = "https://runtime.sagemaker.<region>.amazonaws.com/endpoints/<name>/invocations"

async def worker(session, payload):
    t0 = time.time()
    async with session.post(ENDPOINT_URL, data=json.dumps(payload)) as r:
        txt = await r.text()
        return r.status, (time.time() - t0) * 1000, txt[:200]

async def run(n=50):
    payload = {"inputs": "hello"}
    timeout = aiohttp.ClientTimeout(total=80)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        tasks = [worker(session, payload) for _ in range(n)]
        return await asyncio.gather(*tasks, return_exceptions=True)

print(asyncio.run(run(30)))

위 코드에서 ENDPOINT_URL에 포함된 <region>, <name> 같은 표기는 MDX에서 태그로 오인될 수 있으니 실제 문서/코드에서는 반드시 백틱으로 감싸거나 &lt; &gt;로 치환해 관리하는 습관이 안전합니다.

(2) 재시도는 “무조건”이 아니라 “조건부”로

SageMaker 502/타임아웃에 재시도를 걸면 일시적 장애는 완화되지만, 과부하 상황에서는 트래픽을 더 밀어 넣어 상황을 악화시킬 수 있습니다.

권장 패턴:

  • 5XX 또는 네트워크 오류에만 제한적 재시도
  • 지수 백오프 + 지터
  • 총 재시도 시간 상한(예: 2회, 3회)
  • 멱등성 보장(같은 요청을 여러 번 보내도 안전한지)

재시도/백오프 설계는 아래 글의 프레임이 그대로 적용됩니다.

자주 발생하는 원인별 빠른 처방 요약

  • ModelLatency가 높다: 전처리/토크나이저 최적화, 입력 길이 제한, 모델 경량화, 인스턴스 상향
  • OverheadLatency가 높다: 페이로드 축소, 직렬화 비용 점검, 불필요한 필드 제거
  • 5XX가 튄다(지연은 낮음): 컨테이너 예외/크래시, OOM, 워커 설정, 헬스체크/모델 로딩 경로 점검
  • 트래픽 급증 때만 장애: 최소 인스턴스 상향, 스케일링 정책 재조정, 쿨다운/목표값 튜닝

결론: 502·타임아웃은 “증상”이고, 지표가 원인이다

SageMaker 엔드포인트 502/타임아웃은 하나의 원인으로 설명되지 않는 경우가 많습니다. 하지만 ModelLatency/OverheadLatency/5XX/인스턴스당 요청 수를 기준으로 보면, 문제는 대개 (1) 모델 처리 시간이 길어 큐잉이 발생하거나 (2) 컨테이너가 불안정(OOM/예외/워커 부족) 하거나 (3) 스케일링이 늦어 순간 트래픽을 못 받는 것으로 수렴합니다.

위 7단계를 순서대로 적용하면, “타임아웃을 늘려서 덮는” 방식이 아니라 P99를 낮추고 실패를 통제하는 운영 상태로 갈 수 있습니다.