- Published on
GCP Cloud Run 503/504 원인별 해결 - 타임아웃·동시성
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Cloud Run을 운영하다 보면 가장 사람을 불안하게 만드는 응답이 503 과 504 입니다. 둘 다 “서버가 요청을 처리하지 못했다”는 느낌이지만, 원인은 꽤 다릅니다. 특히 Cloud Run은 요청 타임아웃, 컨테이너 동시성, 인스턴스 스케일링, 콜드스타트, 업스트림(DB/외부 API) 병목이 얽히면서 같은 증상이 다른 원인으로 나타납니다.
이 글은 503/504 를 “에러 코드”가 아니라 “리소스/시간/동시성의 경고등”으로 보고, Cloud Run에서 자주 발생하는 원인별로 빠르게 좁혀가는 방법과 설정/코드 레벨 해결책을 정리합니다.
또한 DB 커넥션이 얽혀 있는 경우가 많아, 커넥션 고갈 진단은 아래 글과 함께 보면 원인 분리가 빨라집니다.
503 vs 504: Cloud Run에서 의미가 달라지는 지점
503 Service Unavailable가 흔히 의미하는 것
Cloud Run에서 503 은 대개 아래 중 하나입니다.
- 트래픽을 받을 준비가 된 인스턴스가 없음
- 인스턴스가 뜨는 중이지만 컨테이너가 리스닝 포트에 바인딩하지 못함
- 동시성/리소스 압박으로 요청 큐가 처리되지 못하고 실패
- 업스트림(Cloud Run 앞단 프록시)에서 백엔드로 라우팅 실패
특히 “갑자기” 503 이 늘면, 스케일링이 따라가지 못했거나(콜드스타트 포함) 컨테이너가 죽거나, 과도한 동시성으로 내부 병목이 터졌을 가능성이 큽니다.
504 Gateway Timeout가 흔히 의미하는 것
504 는 “프록시가 백엔드 응답을 기다리다 타임아웃”입니다. Cloud Run에서는 보통 다음 케이스가 많습니다.
- Cloud Run 서비스의 요청 타임아웃에 걸림
- 요청은 살아있지만 DB/외부 API 호출이 느려서 응답이 늦음
- 대량 동시 요청으로 인해 애플리케이션이 큐잉되면서 응답이 늦음
즉 504 는 “처리 중이긴 했는데 늦었다” 쪽에 가깝습니다.
1) 타임아웃이 원인인 504: 설정과 설계로 나눠 해결
Cloud Run 요청 타임아웃 확인
Cloud Run 서비스에는 요청 타임아웃이 있습니다. 긴 작업이 있을 때 기본값(또는 현재 설정)이 짧으면 504 로 보일 수 있습니다.
다음 명령으로 현재 설정을 확인합니다.
gcloud run services describe SERVICE_NAME \
--region REGION \
--format="value(spec.template.spec.timeoutSeconds)"
타임아웃을 늘리는 것은 응급처치로 유효하지만, 장기적으로는 “긴 작업을 HTTP 요청에 붙잡아두는 구조” 자체를 바꾸는 게 더 안전합니다.
타임아웃 늘리기(응급처치)
gcloud run services update SERVICE_NAME \
--region REGION \
--timeout=300
- 타임아웃을 늘리면
504는 줄 수 있지만 - 동시성/인스턴스 비용이 증가할 수 있고
- 느린 의존성 호출이 방치될 수 있습니다
긴 작업은 비동기화(정공법)
HTTP 요청은 빠르게 202 등으로 응답하고, 작업은 큐로 넘기는 패턴이 Cloud Run과 잘 맞습니다.
- Cloud Tasks
- Pub/Sub
- Workflows
예: 요청은 작업 생성만 하고 즉시 반환
// express 예시
app.post('/reports', async (req, res) => {
const jobId = await enqueueReportJob(req.body); // Cloud Tasks 또는 Pub/Sub
res.status(202).json({ jobId });
});
이렇게 바꾸면 “타임아웃을 늘려서 버티기”가 아니라 “타임아웃이 나올 구조를 제거”할 수 있습니다.
업스트림 호출에 타임아웃을 반드시 설정
Cloud Run의 타임아웃만 믿고 외부 호출을 무한 대기시키면, 요청이 누적되어 결국 동시성 병목으로 번집니다.
Node.js fetch 예시(AbortController 사용):
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 2500);
try {
const r = await fetch(process.env.UPSTREAM_URL, {
signal: controller.signal,
});
return await r.json();
} finally {
clearTimeout(t);
}
Java WebClient 예시:
HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofSeconds(3));
WebClient client = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
핵심은 “외부가 느리면 내 서비스도 같이 멈춘다”를 차단하는 것입니다.
2) 동시성이 원인인 503/504: Cloud Run Concurrency를 재설계
Cloud Run의 컨테이너 동시성(concurrency) 은 인스턴스 하나가 동시에 처리할 수 있는 요청 수입니다.
- 동시성이 너무 높으면: CPU/메모리/DB 커넥션이 고갈되어
503/504증가 - 동시성이 너무 낮으면: 인스턴스가 과도하게 늘어 비용 증가, 콜드스타트 빈도 증가
증상으로 보는 “동시성 과다” 신호
- 평균 지연이 아니라 p95/p99 지연이 급증
- 에러는 없는데 응답이 늦다가 갑자기
504 - DB 커넥션 고갈, 외부 API rate limit, 스레드풀 고갈
DB 커넥션이 의심되면 아래 글의 체크리스트가 그대로 적용됩니다.
동시성 낮추기
gcloud run services update SERVICE_NAME \
--region REGION \
--concurrency=10
실무 팁:
- DB를 쓰는 API 서버라면
10이하부터 시작해 관측하며 올리는 편이 안전합니다. - CPU가
1vCPU이고 동기 블로킹 작업이 많다면 동시성을 크게 주면 지연이 폭발하기 쉽습니다.
동시성과 DB 풀 사이징을 같이 맞추기
컨테이너 동시성이 C 이고, 요청 하나가 DB 커넥션을 오래 잡는다면 커넥션 풀은 최소 C 이상이 필요합니다. 하지만 풀을 무작정 키우면 DB 서버가 먼저 죽습니다.
권장 접근:
- 동시성을 먼저 낮춰서 인스턴스당 DB 부하를 제한
- 요청당 DB 점유 시간을 줄이기(쿼리 최적화, 트랜잭션 범위 축소)
- 그 다음 풀을 “필요 최소”로
Spring Boot HikariCP 예시(인스턴스당 풀 크기 제한):
spring:
datasource:
hikari:
maximum-pool-size: 10
minimum-idle: 2
connection-timeout: 2000
여기서 Cloud Run 동시성이 10 인데 풀도 30 이면, 트래픽 급증 시 DB가 먼저 한계에 도달할 수 있습니다.
CPU 할당 정책이 동시성에 미치는 영향
Cloud Run은 CPU를 요청 처리 중에만 주는 설정이 일반적입니다. 백그라운드 작업이 있거나, 요청 처리 중 CPU가 부족하면 지연이 늘어 504 로 이어질 수 있습니다.
- CPU를 늘리거나
- 동시성을 줄이거나
- 백그라운드 작업을 분리
중 하나로 해결하는 게 일반적입니다.
3) 콜드스타트/스케일링 지연이 원인인 503: min instances와 준비성
트래픽이 갑자기 증가할 때 새 인스턴스가 뜨는 동안 요청이 밀리면 503 이 보일 수 있습니다.
min instances로 워밍 유지
gcloud run services update SERVICE_NAME \
--region REGION \
--min-instances=1
- 사용자-facing API(로그인, 결제 등)는
1이상을 권장하는 경우가 많습니다. - 비용과 지연 안정성을 트레이드오프로 봐야 합니다.
startup probe 관점: “리스닝 포트 바인딩”이 늦는 경우
Cloud Run은 컨테이너가 PORT 로 리스닝해야 트래픽을 붙입니다. 앱이 초기화 중에 포트를 늦게 열면 그 사이 503 이 늘 수 있습니다.
Node/Express 예시:
const express = require('express');
const app = express();
// 가능하면 서버를 먼저 열고,
// 무거운 초기화는 lazy init 또는 별도 경로로 분리
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`listening on ${port}`);
});
무거운 초기화(대형 모델 로딩, 대규모 캐시 워밍, 마이그레이션 등)는 요청 경로에서 분리하거나, 기동 단계에서 꼭 필요하지 않다면 지연 로딩으로 바꾸는 것이 좋습니다.
4) 메모리 부족/프로세스 크래시가 원인인 503: OOM과 재시작
인스턴스가 OOM으로 죽으면 순간적으로 503 이 튈 수 있습니다. 다음을 확인합니다.
- Cloud Logging에서 컨테이너 종료 로그
- Cloud Monitoring에서 메모리 사용량
대응:
- 메모리 증설
- 이미지/라이브러리 슬림화
- 요청당 메모리 사용량 줄이기(대용량 JSON, 파일 처리 스트리밍)
예: Node에서 대용량 응답을 한 번에 만들지 않고 스트리밍.
5) 로드밸런서/클라이언트 타임아웃이 원인인 504: 경로 전체를 점검
Cloud Run 자체 타임아웃이 충분해도, 앞단에 다음이 있으면 더 짧은 타임아웃으로 504 가 날 수 있습니다.
- HTTP(S) Load Balancer
- API Gateway
- 클라이언트(브라우저, 모바일 SDK, 서버 간 호출)의 타임아웃
체크 포인트:
- “어디에서
504를 생성했는지”를 로그로 구분 traceId를 전달해 요청 경로를 end-to-end로 추적
Node에서 상관관계 ID를 로그에 포함하는 간단 예시:
import crypto from 'crypto';
app.use((req, res, next) => {
const id = req.header('x-request-id') || crypto.randomUUID();
res.setHeader('x-request-id', id);
req.requestId = id;
next();
});
app.get('/health', (req, res) => {
console.log({ requestId: req.requestId, path: req.path });
res.status(200).send('ok');
});
이렇게 해두면 503/504 가 “Cloud Run 문제인지, 앞단 문제인지, 업스트림 문제인지” 분리가 빨라집니다.
6) 관측으로 원인을 확정하는 체크리스트(실전)
로그에서 먼저 볼 것
503이 특정 시점에 몰리는지(배포 직후, 트래픽 스파이크 직후)504직전에 애플리케이션 로그가 “느리게” 이어지는지(외부 호출 대기)- 컨테이너 재시작/OOM 흔적이 있는지
메트릭에서 먼저 볼 것
- 요청 지연 p95/p99
- 인스턴스 수 증감(스케일아웃이 늦는지)
- CPU/메모리 사용량
빠른 결론을 위한 매핑
504+ 지연 p99 급증 + 외부 API 호출 느림: 업스트림 타임아웃/재시도/서킷브레이커 필요504+ CPU 100% + 동시성 높음: 동시성 낮추고 CPU 증설 검토503+ 인스턴스가 0에서 급증: min instances로 완화, 콜드스타트 최적화503+ OOM/크래시: 메모리 증설 또는 메모리 누수/대용량 처리 개선
7) 추천 운영 설정 조합(출발점)
서비스 성격에 따라 다르지만, 자주 쓰는 출발점은 다음과 같습니다.
사용자-facing API
--min-instances=1--concurrency=5또는10- 타임아웃은
60~120초 내에서 설계(긴 작업은 비동기)
배치성/웹훅 처리
--min-instances=0- 동시성은 작업 특성에 맞게(외부 API rate limit 있으면 낮게)
- 재시도는 idempotency 보장 후 적용
8) 배포 후 검증: 재현 가능한 부하 테스트 스크립트
동시성/타임아웃 튜닝은 추측이 아니라 재현으로 해야 합니다. 간단히 hey 로 확인할 수 있습니다.
hey -n 2000 -c 50 https://YOUR_RUN_URL/api
-c를 올리면서 p95/p99, 에러 비율을 같이 봅니다.- 이때 동시성(
--concurrency)과 인스턴스 리소스(CPU/메모리)를 바꿔가며 “가장 비용 대비 안정적인 지점”을 찾습니다.
마무리: 503/504는 설정 하나가 아니라 균형 문제
Cloud Run의 503/504 는 단일 원인이라기보다 시간(타임아웃) 과 공유 자원(동시성, CPU, DB 커넥션) 의 균형이 깨졌다는 신호인 경우가 많습니다.
504는 먼저 “긴 작업을 HTTP에 묶어두지 않았는지”, “업스트림 타임아웃이 있는지”를 확인하고503는 “준비된 인스턴스가 없었던 이유(콜드스타트, 크래시, 스케일링 지연)”를 확인한 뒤- 동시성, min instances, CPU/메모리, 외부 호출 타임아웃을 함께 튜닝하면 재발이 크게 줄어듭니다.
DB 커넥션 고갈이 함께 보인다면, Cloud Run 튜닝과 별개로 애플리케이션 풀/쿼리/트랜잭션 설계를 같이 손보는 것이 가장 빠른 해결책입니다.