- Published on
GCP Cloud Run 503·콜드스타트 줄이는 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Cloud Run을 운영하다 보면 트래픽이 몰릴 때 간헐적으로 503이 튀거나, 첫 요청이 유독 느린 콜드스타트 때문에 SLO가 흔들리는 일이 잦습니다. 특히 min instances=0로 비용 최적화를 해둔 서비스는 새 인스턴스가 올라오는 순간의 지연이 사용자 체감으로 직결됩니다.
이 글은 Cloud Run의 503을 “정말로 앱이 죽어서 나는 503”과 “플랫폼이 인스턴스를 제때 못 붙여서 나는 503”로 분해하고, 콜드스타트를 줄이기 위한 튜닝을 설정 레벨과 코드 레벨로 나눠 정리합니다. 마지막에는 재현·검증을 위한 체크리스트까지 포함합니다.
1) Cloud Run 503을 먼저 분류하자
Cloud Run의 503 Service Unavailable은 원인이 다양합니다. 튜닝 전에 아래 두 축으로 분류하면 접근이 빨라집니다.
(1) 인스턴스가 부족해서 나는 503
- 갑작스런 트래픽 급증
max instances가 너무 낮음concurrency가 너무 낮아 인스턴스가 폭증해야 하는데 스케일이 늦음- 외부 의존성(예: DB) 병목으로 요청 처리 시간이 길어져 동시 처리량이 떨어짐
이 경우는 “인스턴스가 준비되기 전까지 큐잉이 버티지 못하고 503”이 발생하기 쉽습니다.
(2) 인스턴스는 떴는데 준비가 안 돼서 나는 503
- 앱 부팅 시간이 길어 readiness가 늦음
- 부팅 중 외부 의존성 연결(예: DB 마이그레이션, 원격 설정 로드)이 블로킹
- 컨테이너 이미지가 커서 pull 및 cold start가 늘어남
이 경우는 “새 인스턴스가 요청을 받을 준비가 되기 전에 트래픽이 들어와 실패”가 핵심입니다.
2) 관측부터: 어떤 503인지 로그로 확정하기
Cloud Run에서는 Request log와 Audit/Platform log를 함께 봐야 합니다.
- Cloud Logging에서 서비스별로
resource.type="cloud_run_revision"필터 httpRequest.status=503를 먼저 모으고- 같은 시각대에
instanceId가 바뀌는지(새 인스턴스),latency가 급증하는지 확인
또한 Cloud Monitoring에서 아래 지표를 같이 봅니다.
- 요청 수, 5xx 비율
- 인스턴스 수(활성 인스턴스/최대치 근접 여부)
- 요청 지연 p95/p99
“503이 늘기 직전에 인스턴스 수가 max에 붙는가”와 “새 인스턴스가 늘면서 p99가 튀는가”가 분기점입니다.
3) 설정 튜닝: 콜드스타트와 503을 동시에 줄이는 핵심 옵션
3.1 min instances로 콜드스타트 자체를 제거
가장 확실한 방법은 min instances를 1 이상으로 두는 것입니다. 비용이 들지만 효과는 즉시입니다.
- 사용자-facing API:
min instances=1~N - 배치/드문 호출:
min instances=0유지
CLI 예시:
gcloud run services update my-svc \
--region=asia-northeast3 \
--min-instances=1
팁:
- 트래픽 패턴이 “업무 시간에만 많다”면 스케줄링으로 업무 시간대만
min instances를 올리는 방식도 가능합니다.
3.2 max instances 상한이 503의 직접 원인이 되는지 확인
max instances가 너무 낮으면 스케일이 필요한 순간에 더 늘지 못해 503이 나기 쉽습니다.
gcloud run services update my-svc \
--region=asia-northeast3 \
--max-instances=200
단, 무작정 올리면 외부 의존성(예: DB 커넥션, Redis, 서드파티 API)도 함께 폭증합니다. 특히 DB는 커넥션 폭증이 곧 장애로 이어지므로, 아래 “동시성·커넥션 풀”과 같이 맞춰야 합니다.
관련해서 DB 병목이 의심된다면, 애플리케이션 이전에 DB의 정렬/임시 테이블 폭증 같은 문제도 함께 점검하는 편이 빠릅니다. 예: MySQL 8 filesort·tmp table 폭증 튜닝 실전
3.3 concurrency로 인스턴스 수와 지연을 균형 잡기
Cloud Run은 기본적으로 한 인스턴스가 여러 요청을 동시에 처리할 수 있습니다. concurrency를 너무 낮게 두면 인스턴스가 과도하게 늘어 스케일 지연과 503이 생기기 쉽고, 너무 높으면 한 인스턴스 내에서 GC/락/스레드 경합으로 지연이 튈 수 있습니다.
- CPU 바운드 서비스:
concurrency를 낮게(예: 10~30) - I/O 바운드 서비스:
concurrency를 높게(예: 50~200)
gcloud run services update my-svc \
--region=asia-northeast3 \
--concurrency=80
실전 팁:
p95 latency가 올라가는데 인스턴스는 충분하면concurrency과다 가능성이 큽니다.- 503이 나오는데 인스턴스가
max에 붙지 않는다면, readiness/부팅/타임아웃 쪽을 우선 의심합니다.
3.4 CPU 할당 정책: startup 동안 CPU가 없으면 부팅이 늘어진다
Cloud Run은 유휴 시 CPU를 제한하는 정책이 있습니다. 콜드스타트 자체는 “인스턴스 생성+부팅”이므로, startup 시점의 CPU가 충분치 않으면 부팅이 길어지고 readiness가 늦어져 503 가능성이 올라갑니다.
- 요청 처리량이 중요하고 부팅이 무거운 서비스: CPU always allocated 고려
- 비용이 중요하고 트래픽이 희소: 기본 정책 유지
gcloud에서 CPU 정책은 플래그로 제어합니다(환경/런타임에 따라 지원 옵션이 다를 수 있으니 콘솔과 함께 확인하세요).
3.5 요청 타임아웃과 플랫폼 타임아웃을 현실적으로 맞추기
콜드스타트가 길거나 외부 의존성 호출이 느린데 타임아웃이 짧으면 503이 아니라 504/timeout 형태로도 흔들립니다.
gcloud run services update my-svc \
--region=asia-northeast3 \
--timeout=30
- 무작정 늘리면 “느린 성공”이 늘어 UX가 나빠질 수 있으니, p95/p99 기반으로 조정합니다.
4) 코드 튜닝: 콜드스타트 시간을 줄이는 실제 포인트
콜드스타트는 대체로 아래 항목의 합입니다.
- 컨테이너 이미지 pull 및 시작
- 런타임 초기화(JVM/Node/Python 등)
- 애플리케이션 부팅(스프링 컨텍스트, DI, 라우팅)
- 외부 의존성 연결(DB, 캐시, 설정 서버)
4.1 “부팅 시 외부 호출”을 지연 로딩으로 바꾸기
가장 흔한 실수는 앱 시작 시점에 아래를 동기적으로 처리하는 것입니다.
- DB 연결 확인 및 마이그레이션
- 원격 설정/시크릿/피처플래그 로드
- 외부 API warm-up
원칙:
- readiness가 true가 된 뒤에 백그라운드로 준비
- 첫 요청에서 꼭 필요하지 않은 작업은 lazy init
Node.js 예시(지연 초기화 + 단일 초기화 보장):
let initialized = false;
let initPromise = null;
async function initOnce() {
if (initialized) return;
if (!initPromise) {
initPromise = (async () => {
// 예: 원격 설정 로드, 캐시 준비 등
await loadRemoteConfig();
initialized = true;
})();
}
await initPromise;
}
export async function handler(req, res) {
await initOnce();
res.status(200).send("ok");
}
핵심은 “동시 요청이 들어와도 초기화가 한 번만 수행”되도록 하는 것입니다.
4.2 DB 커넥션 풀을 Cloud Run 스케일에 맞게 제한
max instances를 올리면 DB 커넥션도 같이 늘어납니다. 인스턴스당 풀 사이즈가 크면 DB가 먼저 죽고, 그 결과로 앱에서 5xx가 연쇄적으로 발생합니다.
- 인스턴스당 커넥션 풀을 작게 잡고
concurrency와 함께 처리량을 맞춥니다.
예: Spring Boot HikariCP의 경우(환경변수로 주입 권장)
spring:
datasource:
hikari:
maximum-pool-size: 5
minimum-idle: 0
connection-timeout: 2000
추가 팁:
- Cloud SQL을 쓴다면 Cloud SQL 커넥터/프록시 사용 패턴에 따라 연결 비용이 달라집니다.
- 쿼리 자체가 느리면 풀을 늘려도 해결되지 않습니다. 이때는 쿼리/인덱스/정렬 비용부터 줄여야 합니다.
4.3 이미지 크기 줄이기: 콜드스타트의 “숨은 1초” 없애기
컨테이너 이미지가 크면 pull 시간이 늘어 콜드스타트가 느려집니다.
- 멀티 스테이지 빌드로 런타임에 필요한 파일만 포함
- 불필요한 빌드 도구/캐시 제거
- 가능한 distroless 또는 slim 베이스 사용
Dockerfile 예시(멀티 스테이지):
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
CMD ["dist/server.js"]
4.4 헬스 체크(ready)와 앱 라우팅을 단순하게
Cloud Run은 Kubernetes의 readinessProbe를 직접 설정하는 방식과는 다르지만, 애플리케이션 관점에서는 “요청을 받자마자 200을 줄 수 있는 경로”가 중요합니다.
/healthz같은 경로는 DB 의존 없이 빠르게 응답- readiness는 “서비스 가능 여부”로 최소 조건만 확인
- DB까지 포함한 deep health는 별도 경로로 분리
Express 예시:
app.get("/healthz", (req, res) => {
res.status(200).send("ok");
});
app.get("/healthz/deep", async (req, res) => {
await db.ping();
res.status(200).send("ok");
});
이렇게 분리하면 부팅 직후 deep check 실패로 트래픽이 튕기는 상황을 줄일 수 있습니다.
5) 503을 줄이는 트래픽/아키텍처 측면의 보완
5.1 Cloud Run 앞단 캐시·CDN로 급증을 완화
정적 응답 또는 캐시 가능한 API라면 Cloud CDN/프록시 캐시로 Cloud Run 도달 QPS 자체를 낮추는 게 가장 강력합니다.
- 캐시 히트율이 올라가면 인스턴스 스케일이 덜 필요
- 스케일 지연으로 인한 503도 동반 감소
5.2 외부 의존성 병목을 먼저 제거
Cloud Run은 스케일이 빠른 편이지만, DB나 파일 시스템 같은 의존성은 그렇지 않습니다. 병목이 의존성에 있으면 Cloud Run만 튜닝해도 503이 계속 납니다.
- DB 쿼리 최적화
- N+1 제거(특히 Spring/JPA)
관련 글: Spring Boot 3 JPA N+1 폭발을 끝내는 법
6) 재현과 검증: 튜닝 전후를 수치로 확인하는 방법
6.1 부하 테스트로 콜드스타트/스케일 구간을 의도적으로 만들기
min instances=0상태에서 10~30분 무트래픽 후 첫 요청 측정- 이후 QPS를 계단식으로 올려 503이 발생하는 지점을 찾기
hey 예시:
hey -z 60s -c 50 -q 10 https://my-svc-xxxxx.a.run.app/api
측정 포인트:
- 첫 요청 latency
- p95/p99
- 5xx 비율
- 인스턴스 수 증가 곡선
6.2 변경은 한 번에 하나씩
min instances, concurrency, max instances, 이미지 최적화, 코드 지연 로딩을 한 번에 바꾸면 원인-결과가 섞입니다.
추천 순서:
min instances=1로 콜드스타트 체감 제거(가장 즉효)concurrency조정으로 인스턴스 폭증 완화max instances상향 및 DB 커넥션 제한 동시 적용- 이미지/부팅 최적화로 비용 대비 성능 개선
7) 실전 체크리스트
503발생 시각에 인스턴스 수가max에 붙는가- 새 인스턴스 증가 시 p99가 튀는가(콜드스타트)
min instances를 올렸을 때 503과 첫 요청 지연이 즉시 줄어드는가concurrency를 올렸을 때 인스턴스 수는 줄고 지연은 유지되는가- DB 커넥션이 인스턴스 스케일과 함께 폭증하지 않는가
- 부팅 시 외부 호출을 lazy init로 바꿨는가
- 컨테이너 이미지가 불필요하게 크지 않은가
마무리
Cloud Run의 503과 콜드스타트는 “플랫폼 문제”처럼 보이지만, 대부분은 min instances/concurrency/max instances의 균형 실패 또는 “부팅 시점의 과도한 초기화”에서 시작합니다. 먼저 503을 유형별로 분류해 관측하고, 설정 튜닝으로 큰 레버를 조정한 뒤 코드와 이미지 최적화로 콜드스타트의 잔여 시간을 깎아내리면 재발률이 눈에 띄게 줄어듭니다.
운영 중인 서비스가 DB 의존이 크다면 Cloud Run 튜닝과 함께 DB 병목을 반드시 병행 점검하세요. 작은 설정 변화가 외부 의존성에 큰 부하를 줄 수 있고, 그게 다시 503으로 되돌아오는 경우가 가장 흔합니다.