Published on

AWS ALB 502/504 급증 - 타임아웃 7곳 점검

Authors

ALB(Application Load Balancer) 뒤의 서비스가 멀쩡해 보이는데도 갑자기 502/504가 치솟는 상황은 보통 “어딘가에서 먼저 끊었다”는 신호입니다. 중요한 포인트는 타임아웃은 한 군데가 아니라 여러 계층에 존재하고, 이 값들이 서로 어긋날 때 오류가 폭발적으로 나타난다는 점입니다.

이 글에서는 ALB 기준으로 502/504가 급증할 때 타임아웃 관련 7곳을 우선순위대로 점검하는 방법을 정리합니다. 각 항목은 “증상 → 확인 방법 → 권장 설정/해결” 흐름으로 설명합니다.

관련해서 네트워크/타임아웃 계열 이슈를 더 깊게 보고 싶다면 다음 글도 함께 참고하면 좋습니다.

0) 먼저: 502와 504를 ALB 관점에서 구분하기

ALB에서 흔히 보는 의미를 “디버깅 관점”으로 단순화하면 아래처럼 정리할 수 있습니다.

  • 504(Gateway Timeout): ALB가 타겟으로부터 제시간에 응답을 못 받음
  • 502(Bad Gateway): ALB가 타겟과의 연결/응답을 정상적으로 해석 못 함 (연결 리셋, 잘못된 헤더, 조기 종료 등)

실무에서는 504가 “느림/정체/대기” 쪽, 502가 “중간에 끊김/프로토콜 불일치/리셋” 쪽으로 많이 나타납니다. 하지만 둘 다 결국 시간 초과나 연결 종료가 뿌리인 경우가 많습니다.

1) ALB Idle timeout (클라이언트-ALB, ALB-타겟)

가장 먼저 확인할 값입니다. ALB에는 idle_timeout이 있고, 기본값은 60초입니다(환경/시점에 따라 다를 수 있으니 콘솔에서 확인).

증상

  • 특정 요청만 60초 근처에서 뚝 끊기며 502 또는 504가 증가
  • 스트리밍/SSE/롱폴링/대용량 업로드에서 특히 자주 발생

확인 방법

  • ALB 속성에서 idle_timeout 확인
  • CloudWatch 지표에서 HTTPCode_ELB_5XX_Count, TargetResponseTime, RequestCount의 동시 변화를 확인

해결 가이드

  • 애플리케이션의 “최대 응답 시간”과 “업스트림 타임아웃”을 기준으로 idle_timeout을 조정
  • 단, 무작정 크게 올리면 커넥션 점유가 길어져 리소스/동시성 문제를 악화시킬 수 있으니 서버 처리시간을 줄이거나 비동기화와 함께 접근

Terraform 예시(인라인 코드로 안전 처리):

resource "aws_lb" "app" {
  name               = "app-alb"
  load_balancer_type = "application"

  idle_timeout = 120
}

2) Target group health check 타임아웃과 임계치

헬스체크는 타임아웃 불일치의 “증폭기” 역할을 합니다. 실제 트래픽은 견디는데 헬스체크만 느려져서 타겟이 빠지면, 남은 타겟에 부하가 몰리며 502/504가 연쇄적으로 증가할 수 있습니다.

증상

  • 특정 AZ/특정 타겟만 번갈아 OutOfService
  • 배포 직후나 트래픽 급증 시점에 헬스체크 실패가 급증

확인 방법

  • Target group의 Health check timeout, Interval, Healthy threshold, Unhealthy threshold 확인
  • 타겟 인스턴스/파드에서 헬스체크 엔드포인트 로그 확인

해결 가이드

  • 헬스체크 엔드포인트는 DB/외부 API에 의존하지 않게 최소화
  • 초기화가 느린 앱이라면 “부팅 완료 전에는 트래픽을 받지 않도록” readiness 개념을 분리

AWS CLI 예시:

aws elbv2 describe-target-groups \
  --names my-tg \
  --query 'TargetGroups[0].HealthCheckTimeoutSeconds'

3) ALB에서 타겟으로 가는 연결: keep-alive, 연결 재사용, 조기 종료

502가 많고, 특히 짧은 요청에서도 간헐적으로 발생한다면 “타겟이 연결을 먼저 닫는” 패턴을 의심해야 합니다. 대표적으로 앱 서버(또는 앞단 프록시)가 keep-alive 정책을 짧게 가져가거나, 커넥션을 재사용하는 동안 서버가 idle로 판단해 먼저 종료하는 경우입니다.

증상

  • 502가 랜덤하게 튀고, 재시도하면 성공
  • 서버 로그에 connection reset by peer 류가 보임

확인 방법

  • 타겟(예: Nginx, Envoy, 앱 서버)의 keep-alive 관련 설정 확인
  • VPC Flow Logs에서 RST 패턴이 보이는지 확인(가능한 경우)

해결 가이드

  • ALB idle timeout과 서버 keep-alive idle timeout의 “관계”를 정리
  • 일반적으로는 서버 쪽 keep-alive idle timeout을 ALB보다 약간 길게 두는 편이 안전(환경에 따라 다름)

Nginx 예시:

http {
  keepalive_timeout  130s;
  proxy_read_timeout 120s;
  proxy_send_timeout 120s;
}

주의: 설정값 자체보다 서로의 우선 종료 주체가 누구인지가 핵심입니다.

4) 애플리케이션 서버(프레임워크/런타임) 요청 타임아웃

ALB는 기다리고 있는데 앱이 먼저 “요청 처리 한도 시간”을 넘겨 종료하면 502로 보일 수 있습니다(응답 헤더 전송 전에 종료, 워커 강제 종료 등).

증상

  • 특정 API에서만 5xx가 집중
  • 앱 로그에 타임아웃 예외가 명확히 기록됨

확인 방법

  • Node.js/Express, Spring, Django, Go 서버 등에서 request timeout 설정 확인
  • 워커 모델(예: Gunicorn, uWSGI, Node cluster)의 worker timeout 확인

해결 가이드

  • “최대 처리시간”을 늘리기 전에, 왜 느린지(락, 외부 API, DB 슬로우쿼리)를 먼저 분해
  • 불가피하게 오래 걸리는 작업은 비동기 잡 큐로 분리하고 API는 202로 즉시 응답

Node.js(예: http server) 예시:

import http from 'http';

const server = http.createServer(async (req, res) => {
  // ...
  res.end('ok');
});

server.requestTimeout = 120_000;
server.headersTimeout = 125_000;
server.listen(3000);

여기서도 포인트는 “값을 키우기”가 아니라 ALB, 프록시, 앱의 타임아웃을 일관되게 맞추는 것입니다.

5) 업스트림(예: Nginx/Envoy) proxy 타임아웃과 버퍼링

ALB 바로 뒤에 Nginx/Envoy가 있고, 그 뒤에 앱이 있는 구조라면 타임아웃은 최소 2단으로 늘어납니다. 이때 흔한 실수는 Nginx의 proxy_read_timeout은 길게 해놓고, send_timeout이나 client_body_timeout은 짧게 둬서 업로드/응답 전송 단계에서 끊기는 경우입니다.

증상

  • 업로드 API에서 502/504 증가
  • 응답 바디가 큰 API에서 간헐적 실패

확인 방법

  • Nginx access/error log에서 upstream timeout, client timeout 메시지 확인
  • Envoy면 upstream request timeout, stream reset reason 확인

해결 가이드

  • 업로드/다운로드/스트리밍 유형별로 타임아웃을 분리
  • 버퍼링(디스크 스풀 포함)이 병목이면 버퍼 설정과 임시 디스크 용량도 함께 점검

Nginx 예시:

location /api/ {
  proxy_connect_timeout 5s;
  proxy_send_timeout    120s;
  proxy_read_timeout    120s;
  send_timeout          120s;
}

6) 데이터 계층 타임아웃(DB, Redis, 외부 API)과 풀 고갈

ALB에서 504가 늘 때 실제 원인은 앱이 DB 커넥션을 못 얻어 대기하거나, 외부 API가 느려서 워커가 묶이는 경우가 많습니다. 이때는 “타임아웃”이 아니라 “대기열”이 문제입니다.

증상

  • 특정 시간대에만 급증(배치/크론/트래픽 피크)
  • 앱 CPU는 낮은데 응답이 느림(대기)

확인 방법

  • DB 슬로우쿼리, 커넥션 수, 락 대기 확인
  • Redis latency, blocked clients 확인
  • 외부 API 호출의 p95/p99와 재시도 폭증 확인

해결 가이드

  • 커넥션 풀 크기 조정은 “증상 완화”일 뿐, 병목 쿼리/락/인덱스가 우선
  • 외부 API는 타임아웃을 짧게, 재시도는 지수 백오프로 제한(동시 폭주 방지)

Go에서 HTTP 클라이언트 타임아웃 예시:

client := &http.Client{
  Timeout: 3 * time.Second,
}

핵심은 “서버는 120초 기다리는데 외부 API는 30초 후 응답” 같은 불일치를 줄이고, 짧은 타임아웃 + 빠른 실패 + 제한된 재시도로 꼬리를 자르는 것입니다.

7) 오토스케일/배포 중 드레이닝과 연결 종료(등록 해제 지연)

ALB 타겟 그룹에는 등록 해제 시 연결을 얼마나 기다릴지(드레이닝)와 관련된 설정이 있습니다. 배포/스케일 인 시점에 연결이 중간에 끊기면 502가 튈 수 있습니다.

증상

  • 배포 시점에만 502가 스파이크
  • 스케일 인 직후부터 짧게 오류가 증가

확인 방법

  • Target group의 deregistration delay(등록 해제 지연) 확인
  • 컨테이너/인스턴스 종료 시그널 처리(예: SIGTERM) 및 graceful shutdown 로그 확인

해결 가이드

  • deregistration delay를 충분히 확보
  • 앱이 종료 시그널을 받으면 즉시 새 요청은 거부하고, 진행 중 요청만 마무리하도록 구현

Kubernetes를 쓴다면 readiness probe와 terminationGracePeriodSeconds 조합도 함께 점검해야 합니다. 이 부분은 K8s CrashLoopBackOff 원인별 로그·Probe 해결 가이드의 probe/종료 섹션이 간접적으로 도움이 됩니다.

간단한 Node.js graceful shutdown 예시:

let shuttingDown = false;

process.on('SIGTERM', () => {
  shuttingDown = true;
  server.close(() => process.exit(0));
});

// 라우터에서 shuttingDown이면 503 반환 등으로 새 요청 차단

관측(Observability) 체크리스트: 어디서 끊기는지 10분 안에 좁히기

타임아웃은 “추측”이 아니라 “상관관계”로 좁혀야 합니다.

  • ALB 액세스 로그를 켜고, target_processing_time, elb_status_code, target_status_code를 함께 본다
  • CloudWatch에서 TargetResponseTime의 p95/p99가 튀는지 확인한다
  • 애플리케이션 로그에 request id(또는 trace id)를 넣고 ALB 로그와 조인한다
  • 가능하면 분산 트레이싱으로 “대기 시간의 구간”(DNS, connect, TLS, TTFB, upstream)을 분해한다

ALB 액세스 로그 Athena 쿼리 예시(컬럼명은 테이블 정의에 맞게 조정):

SELECT
  elb_status_code,
  target_status_code,
  approx_percentile(target_processing_time, 0.95) AS p95_target_time,
  count(*) AS cnt
FROM alb_logs
WHERE from_iso8601_timestamp(time) > now() - interval '1' hour
GROUP BY 1, 2
ORDER BY cnt DESC;

실전 권장 순서(가장 빨리 효과 보는 점검 플로우)

  1. ALB idle_timeout과 발생 시각(60초 근처인지)부터 확인
  2. 타겟 그룹 헬스체크 실패가 동반되는지 확인
  3. 배포/스케일 이벤트와 스파이크가 겹치는지 확인(드레이닝)
  4. 앱/프록시 로그에서 타임아웃 예외 또는 RST 패턴 확인
  5. DB/외부 API 지연 및 풀 고갈 지표 확인
  6. 각 계층 타임아웃 값을 “종단 기준”으로 재정렬

마무리: 타임아웃은 ‘값’이 아니라 ‘계약’이다

ALB 502/504 급증은 대개 “ALB가 나쁘다”가 아니라 클라이언트, ALB, 프록시, 앱, DB/외부 API까지 이어지는 타임아웃 계약이 깨진 결과입니다. 7곳을 순서대로 점검하면, 단순히 타임아웃을 늘리는 방식이 아니라 “어디서 먼저 끊는지”를 명확히 찾아 재발을 줄일 수 있습니다.

특히 gRPC를 쓰는 환경이라면 애플리케이션의 deadline과 프록시/로드밸런서의 타임아웃 불일치가 더 치명적으로 나타납니다. 이 경우는 EKS에서 gRPC DEADLINE_EXCEEDED 폭증 해결도 함께 보면서 deadline 전파와 재시도 정책까지 같이 정리하는 것을 권합니다.