Published on

Cloud Run 504 Timeout 원인·해결 9가지

Authors

서버리스로 운영하는 Cloud Run은 “자동 확장 + 관리형 프록시” 덕분에 편하지만, 트래픽이 몰리거나 외부 의존성이 느려지는 순간 504 Gateway Timeout이 발생하며 원인 파악이 까다로워집니다. 특히 504는 애플리케이션이 직접 내는 에러가 아니라 앞단(로드밸런서/프록시)이 백엔드 응답을 제때 받지 못했을 때 발생하기 때문에, 앱 코드만 보고는 결론이 안 나는 경우가 많습니다.

이 글에서는 Cloud Run 504를 9가지 대표 원인으로 분류하고, 각각에 대해 증상 → 확인 포인트(로그/메트릭) → 해결책 순으로 정리합니다. (비슷한 결의 타임아웃 이슈로는 Gunicorn Uvicorn Worker timeout 재현과 해결, 쿠버네티스 환경의 EKS에서 ALB Ingress 408 Request Timeout 해결 가이드도 함께 참고하면 진단 감이 빨리 잡힙니다.)

504의 의미: “게이트웨이가 기다리다 포기했다”

Cloud Run 요청 경로는 대략 다음과 같습니다.

  1. Client → 2) Google Front End(GFE)/HTTP(S) Load Balancer(또는 Cloud Run front proxy) → 3) Cloud Run 인스턴스(컨테이너) → 4) 앱/의존성(DB, 외부 API 등)

504는 주로 2) 또는 3) 계층에서 업스트림 응답이 타임아웃났을 때 발생합니다. 따라서 진단은 다음 질문으로 시작하는 게 좋습니다.

  • 컨테이너가 요청을 받긴 받았나? (Cloud Run request log 존재 여부)
  • 받았다면 앱이 응답을 늦게 했나, 아니면 아예 못 했나?
  • 늦어진 원인이 CPU/메모리/스레드 고갈인지, 외부 의존성 지연인지, 콜드스타트/스케일링 지연인지?

1) 요청 처리 시간이 Cloud Run 요청 타임아웃을 초과

증상

  • 특정 API가 오래 걸릴 때만 504
  • Cloud Run 로그에 애플리케이션 응답 로그가 끝까지 안 남거나, 끝나기 직전에 끊김

확인

  • Cloud Run 서비스 설정의 Request timeout
  • Cloud Logging에서 해당 request의 latency(지연) 또는 애플리케이션 측 처리시간

해결

  • 장기 작업은 비동기화(Cloud Tasks / Pub/Sub / Workflows)로 분리
  • 정말 동기 응답이 필요하면 timeout을 늘리되, 근본적으로는 “긴 작업”을 HTTP request에 묶지 않는 구조가 안정적

예시: 긴 작업을 Cloud Tasks로 분리 (Python)

# flask 예시: 요청은 즉시 ack, 실제 작업은 task로
from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.post("/process")
def process():
    payload = request.json
    # Cloud Tasks target URL로 비동기 작업 요청
    requests.post("https://tasks-handler-xxxxx.a.run.app/do-work", json=payload, timeout=3)
    return jsonify({"status": "accepted"}), 202

2) 콜드 스타트 + 초기화(의존성 로딩) 과다

증상

  • 배포 직후/트래픽이 뜸할 때 첫 요청만 504
  • 이미지가 크거나, 앱 시작 시 DB 마이그레이션/모델 로딩 등을 수행

확인

  • Cloud Run 인스턴스 시작 로그(컨테이너 부팅)와 첫 요청의 시간 간격
  • Container startup time, CPU 사용량 스파이크

해결

  • 컨테이너 이미지 슬림화(멀티스테이지 빌드, 불필요 패키지 제거)
  • 앱 부팅 시 무거운 작업 제거(마이그레이션/인덱스 생성/대용량 모델 로딩 지양)
  • min instances로 웜 인스턴스 유지(비용과 트레이드오프)

예시: Docker 멀티스테이지로 이미지 최소화

FROM python:3.12-slim AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim
WORKDIR /app
COPY --from=base /usr/local /usr/local
COPY . .
CMD ["python", "main.py"]

3) 동시성(concurrency) 과다로 큐잉/스레드 고갈

증상

  • 트래픽 증가 시 504가 급증
  • CPU는 100%에 가깝고, 응답 시간이 꼬리를 길게 끔

확인

  • Cloud Run 설정의 Concurrency
  • 앱 서버(예: Gunicorn/Uvicorn)의 worker 수/스레드 수
  • p95/p99 latency 급등

해결

  • Cloud Run concurrency를 낮춰 인스턴스를 더 빨리 늘리게 하거나
  • 앱 서버 worker를 늘리되 CPU/메모리 한계 내에서 조정
  • CPU-bound 작업은 스레드로 해결이 안 되므로 프로세스 분리/작업 큐 고려

예시: Uvicorn/Gunicorn 튜닝

# CPU 2 vCPU 가정: worker 2~4부터 테스트
gunicorn -k uvicorn.workers.UvicornWorker \
  -w 4 -t 60 \
  --keep-alive 5 \
  app:app

(Worker timeout 관련은 Gunicorn Uvicorn Worker timeout 재현과 해결에서 더 깊게 다룹니다.)

4) CPU 할당 정책(요청 중에만 CPU) 때문에 “백그라운드 작업”이 멈춤

증상

  • 요청 처리 중 생성한 백그라운드 스레드/코루틴이 완료되지 못해 다음 단계가 지연
  • SSE/스트리밍/폴링 구조에서 간헐적 504

확인

  • Cloud Run CPU 설정이 “CPU allocated only during request”인지
  • 요청 종료 후에도 작업이 이어지도록 설계했는지(Cloud Run에서는 위험)

해결

  • 요청 종료 후 작업이 필요하면 Cloud Tasks/PubSub로 넘기기
  • 정말 필요하다면 “항상 CPU 할당(Always on)”을 고려(비용 증가)

5) 외부 의존성(DB/외부 API) 지연 또는 커넥션 풀 고갈

증상

  • DB 쿼리/외부 API 호출이 느려질 때 504
  • 애플리케이션 로그에 timeout, connection pool exhausted 류 메시지

확인

  • Cloud SQL/외부 API latency 메트릭
  • 애플리케이션 APM(Trace)에서 병목 구간

해결

  • 타임아웃을 “무한대”로 늘리기보다 짧게 설정 + 재시도(지수 백오프) + 회로차단기
  • 커넥션 풀 크기 조정 및 인스턴스 스케일과 연동(인스턴스 수 × 풀 크기 = DB 한계 초과 주의)

예시: requests 타임아웃/재시도(파이썬)

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retries = Retry(
    total=3,
    backoff_factor=0.2,
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET", "POST"]
)
session.mount("https://", HTTPAdapter(max_retries=retries))

resp = session.get("https://api.example.com/data", timeout=(2, 5))
resp.raise_for_status()

6) VPC Connector/NAT/라우팅 문제로 egress가 느리거나 막힘

증상

  • 외부로 나가는 호출(서드파티 API, 사설망 DB 등)에서만 504
  • 특정 리전/특정 시간대에만 급격히 악화(네트워크 자원 고갈)

확인

  • Serverless VPC Access Connector 사용 여부
  • Cloud NAT 포트 고갈 가능성(대량 동시 outbound)
  • DNS 해석 지연(아래 7번과도 연관)

해결

  • 커넥터 스케일/throughput 설정 점검(필요 시 커넥터 대역 확장)
  • Cloud NAT 설정(포트/주소) 확장, 연결 재사용(keep-alive) 강화
  • egress를 꼭 VPC로 태워야 하는지 재검토(필요 없는 트래픽은 direct egress)

7) DNS 지연/해석 실패로 외부 호출이 늦어짐

증상

  • 외부 API 호출이 첫 번째만 느리거나, 간헐적으로 매우 느림
  • 애플리케이션에서 hostname 기반 호출 시만 문제

확인

  • 애플리케이션 로그에 NameResolutionError, Temporary failure in name resolution
  • 같은 대상에 IP로 호출하면 정상

해결

  • HTTP 클라이언트 keep-alive로 연결 재사용
  • DNS TTL/캐시 전략(애플리케이션 레벨 캐시, 라이브러리 설정)
  • VPC 사용 시 Cloud DNS/포워딩 구성 점검

8) 과도한 응답 크기/스트리밍 처리 문제로 프록시가 타임아웃

증상

  • 다운로드/대용량 JSON 응답에서 504
  • 서버는 계속 쓰고 있는데 클라이언트는 중간에 끊김

확인

  • 응답 바디 크기, 압축 여부
  • 스트리밍 응답에서 flush가 제대로 되는지

해결

  • 큰 payload는 Cloud Storage로 오프로드하고 URL만 반환
  • gzip/br 압축 활성화
  • 페이지네이션/필드 선택(select)으로 응답 축소

예시: 결과는 GCS에 저장하고 signed URL 반환

# 개념 예시(의사코드)
# 1) 결과 파일을 gs:// 버킷에 저장
# 2) 클라이언트에는 signed URL만 반환
return {"download_url": signed_url}

9) 스케일링 한계(최대 인스턴스/쿼터) 또는 트래픽 스파이크 흡수 실패

증상

  • 트래픽 급증 시 504가 폭발, 이후 트래픽이 줄면 정상
  • 인스턴스가 더 늘어나야 하는데 증가가 멈춤

확인

  • Cloud Run max instances 설정
  • 프로젝트/리전 쿼터(인스턴스, CPU, 동시 요청 등)
  • 429(자원 부족)와 504가 함께 관측되는지

해결

  • max instances 상향(단, DB/외부 API가 버틸 수 있는지 함께 검토)
  • 사전 워밍(min instances) 또는 트래픽 스파이크를 큐로 흡수(Cloud Tasks)
  • 병목이 DB라면 캐시/읽기 복제/쿼리 튜닝이 먼저

진단 체크리스트: “어디서 시간이 새는가”를 10분 내로 좁히기

운영 중 504를 만나면 아래 순서로 범위를 빠르게 줄일 수 있습니다.

  1. Cloud Run request log 존재 여부
    • 없다면: 앞단(LB/도메인/네트워크)에서 막혔거나 라우팅 문제
    • 있다면: 컨테이너까지는 도달
  2. latency 분해
    • 앱 처리시간이 긴지, 외부 호출이 긴지(Trace/로그)
  3. 콜드 스타트 여부
    • 첫 요청만 느리면 초기화/이미지/의존성 로딩 의심
  4. 동시성/리소스
    • CPU 100%, 메모리 압박(OOM 직전), worker 포화
  5. 의존성 상태
    • DB connection pool, 외부 API rate limit/지연

마무리: 504는 “타임아웃”이 아니라 “설계 신호”인 경우가 많다

Cloud Run의 504는 단순히 timeout 값을 늘린다고 해결되지 않는 경우가 많습니다. 대부분은

  • 긴 작업을 동기 HTTP에 묶어둔 구조,
  • 스케일링/동시성/리소스 튜닝 미스,
  • 외부 의존성에 대한 방어(타임아웃/재시도/회로차단) 부족,
  • 네트워크 경로(VPC/NAT/DNS) 병목

중 하나로 귀결됩니다.

타임아웃이 반복된다면 “어떤 구간이 느린지”를 먼저 계측(로그/트레이싱)하고, 긴 작업은 큐로, 의존성은 짧은 타임아웃+재시도, 동시성은 안전한 범위로 가져가는 쪽이 재발 방지에 가장 효과적입니다.