Nginx keepalive/timeout 불일치 502 복구 플레이북
💡 Key Takeaways
- 간헐적 502는 서버 다운보다 프록시와 업스트림의 연결 수명 불일치에서 시작되는 경우가 많다.
- 복구 순서를 고정하면 재기동 반복 없이 트래픽 손실을 줄일 수 있다.
- keepalive, read timeout, 업스트림 idle timeout을 같은 표로 관리해야 재발을 막을 수 있다.
1) 문제 정의: 장애처럼 보이지만 사실은 연결 수명 불일치였던 상황
월요일 오전 10시 12분, 결제 진입 API에서 502가 분당 40건 정도로 튀기 시작했다. 이상했던 점은 CPU, 메모리, DB 연결 수가 정상인데도 에러가 특정 구간에서만 반복됐다는 것이다. 온콜 초반에는 "백엔드 인스턴스가 죽는 것 아니냐"는 가설로 파드를 순환 재기동했지만, 5분 정도 잠잠했다가 같은 패턴으로 다시 터졌다. 로그를 맞춰 보니 Nginx는 upstream prematurely closed connection을 남겼고, 애플리케이션은 예외 로그가 거의 없었다. 팀에서 방향을 바꾼 계기는 LB 앞단에서 장시간 유휴였던 연결이 재사용되는 순간에만 502가 올라온다는 점을 찾은 뒤였다. 즉 처리 로직 자체보다 "살아 있다고 믿고 재사용한 소켓"이 이미 업스트림 정책으로 닫혀 있었고, 그 시간차가 피크 시간에만 확률적으로 폭발하고 있었다.
2) 판단 기준/원인 분석: 지표를 한 화면에 묶어야 오판을 줄인다
우리는 인프라 장애와 애플리케이션 결함을 빠르게 분리하기 위해 기준을 네 가지로 고정했다. 첫째, 502 비율과 업스트림 응답시간 p95를 함께 본다. 응답시간이 크게 오르지 않는데 502만 튀면 연결 상태 문제일 가능성이 높다. 둘째, Nginx 에러 로그 키워드를 분류한다. connect() failed가 많으면 네트워크/포트 문제, prematurely closed가 많으면 keepalive 수명 불일치 가능성이 커진다. 셋째, 업스트림 idle timeout과 프록시 keepalive timeout을 같은 단위(초)로 맞춰 비교한다. 실제 사고에서는 앱 서버 idle timeout이 30초, Nginx keepalive가 75초였다. 넷째, 재시도 정책이 에러를 증폭하는지 확인한다. 우리 게이트웨이는 502를 즉시 재시도하게 돼 있었고, 죽은 연결 재사용으로 짧은 구간 트래픽을 더 흔들었다. 네 기준을 묶어 보니 원인은 명확했다. "서버가 느린 것"이 아니라 "프록시가 죽은 연결을 살아 있다고 착각한 것"이었다.
3) 실행 절차: 트래픽 보호 -> 연결 정책 정렬 -> 재시도 완화 순서로 복구
복구는 1시간 안에 끝내기 위해 순서를 엄격히 지켰다. 1단계는 서비스 보호였다. 먼저 502가 급증한 결제 라우트만 임시로 카나리 풀로 우회하고, 사용자 체감 영향을 줄이기 위해 프론트의 중복 제출 방지 토큰 만료 시간을 늘렸다. 2단계는 연결 정책 정렬이다. Nginx keepalive_timeout을 25초로 낮추고, 업스트림의 idle timeout은 30초를 유지해 프록시 쪽 수명이 항상 더 짧게 되도록 맞췄다. 동시에 proxy_read_timeout과 proxy_send_timeout을 실제 API p99의 2배 수준으로 재설정해 조기 종료를 막았다. 3단계는 증폭기 제거였다. 게이트웨이의 자동 재시도를 idempotent GET만 허용하고, 결제 POST는 백엔드 에러코드 기반의 제한적 재시도로 바꿨다. 적용 12분 뒤 502 비율은 1.9%에서 0.08%로 떨어졌고, 재기동 없이 안정 상태로 복귀했다.
4) 운영 체크리스트/주의사항: 같은 패턴을 반복하지 않기 위한 최소 장치
재발 방지는 설정 파일 한 번 수정으로 끝나지 않았다. 우리는 배포 파이프라인에 timeout matrix 검증 단계를 넣어 환경별 keepalive, idle timeout, read timeout의 대소 관계가 깨지면 배포를 막도록 했다. 또 대시보드에 502 비율, upstream reset 수, 재시도 횟수, 라우트별 p95를 한 패널로 묶어 "원인-증상"을 같은 시점에 보게 했다. 현장에서 특히 중요했던 운영 장면은 야간 긴급 변경 후 롤백 기준을 미리 수치로 정한 것이다. 예를 들어 10분 이동평균 502가 0.3%를 넘으면 즉시 이전 프록시 설정으로 되돌리고, 그 대신 카나리 비중을 줄여 원인 분석 시간을 확보했다. 마지막 주의사항은, timeout을 무작정 길게 늘려 문제를 숨기지 않는 것이다. 길어진 timeout은 실패를 늦출 뿐이며 연결 누수와 워커 고갈을 더 크게 만든다. 팀 문서에는 "프록시 수명 < 업스트림 수명" 원칙과 변경 후 24시간 관측 항목을 고정해, 다음 온콜이 같은 함정에 빠지지 않게 했다.