S3 Presigned URL 만료 시각 어긋남 장애 복구 플레이북: 시계 오차부터 CDN 캐시까지
💡 Key Takeaways
- 만료 오류는 코드 버그보다 서버 시각 오차, 프록시 헤더 누락, CDN 캐시 정책 충돌에서 자주 시작되므로 원인 축을 먼저 분리해야 합니다.
- 장애 중에는 URL TTL을 무작정 늘리기보다 요청 경로를 단순화하고 서명 생성 주체를 한 곳으로 모아야 복구가 빨라집니다.
- 복구 후에는 NTP 점검, 캐시 무효화 규칙, 클라이언트 재시도 정책을 한 문서로 묶어야 같은 증상이 반복되지 않습니다.
S3 Presigned URL 만료 시각 어긋남 장애 복구 플레이북
Presigned URL 장애는 겉으로 보면 단순히 Request has expired 한 줄이지만, 실제 현장에서는 시간축이 꼬인 분산 시스템 문제인 경우가 많습니다. 지난달 우리 팀도 프로필 이미지 업로드 API를 배포한 직후 403 비율이 1%에서 19%까지 치솟았고, 초반에는 프런트엔드가 만료 시간을 잘못 계산했다고 의심했습니다. 그런데 로그를 묶어 보니 모바일 앱과 웹이 동시에 실패했고, 특히 CDN을 한 번 거친 경로에서만 실패가 급증했습니다. 이때 바로 해야 할 일은 "URL 생성 시각", "클라이언트 요청 시각", "S3가 판단한 만료 시각"을 같은 타임존으로 정렬해 보는 것입니다. 저는 장애 대응 때 세 줄을 고정으로 확인합니다. 서명 생성 서버의 시스템 시간, 응답 헤더의 Date, 실제 요청이 S3에 도착한 시각입니다. 세 값이 30초 이상 벌어지면 앱 코드보다 인프라 시계 오차나 프록시 지연을 먼저 의심해야 합니다. 처음 10분에 이 축을 잡아두면, 쓸데없는 코드 롤백으로 시간을 버리지 않습니다.
원인 분류는 크게 네 갈래로 나누는 게 효율적입니다. 첫째, 서명 파라미터 자체 문제(X-Amz-Expires 값, X-Amz-Date 포맷, 서명 대상 경로 불일치). 둘째, 시간 동기화 문제(NTP 불안정, 컨테이너 노드 시계 드리프트, VM 스냅샷 복원 후 오차). 셋째, 전송 경로 문제(CDN 캐시로 오래된 URL 응답 재사용, 리버스 프록시가 쿼리스트링 일부를 정규화). 넷째, 클라이언트 사용 패턴 문제(발급 후 장시간 보관했다가 뒤늦게 사용, 백그라운드 재시도로 만료 URL 반복 호출). 실제 사고에서는 이 갈래가 섞여 나타납니다. 우리 팀 케이스도 업로드 URL TTL 120초 자체는 충분했지만, 특정 엣지 구간에서 오래된 JSON 응답이 60초 캐시되면서 사용 가능한 시간이 절반으로 줄었습니다. 그래서 장애 중에는 "TTL 늘리기"를 마지막 카드로 둡니다. 먼저 URL 응답 캐시를 우회하거나 Cache-Control: no-store를 강제하고, 서명 생성 엔드포인트를 단일 서비스로 고정해 시간 기준을 하나로 맞추는 편이 효과가 컸습니다.
실행 절차는 30분 단위로 끊어야 팀이 흔들리지 않습니다. 0~10분에는 실패 요청 샘플 20건을 뽑아 공통 패턴을 찾습니다. 실패한 객체 키, 사용자 리전, 앱 버전, 요청 지연 시간을 붙이면 어느 구간에서 손실되는지 보입니다. 10~20분에는 임시 완화 조치를 적용합니다. 예를 들어 URL TTL을 120초에서 300초로 한시 상향하되, 동시에 업로드 시작 전 재발급 API를 추가해서 오래된 URL 재사용을 차단합니다. 20~30분에는 재발 방지 설정을 최소 3개 묶어 배포합니다. 저는 이때 "NTP 동기화 상태 알림", "URL 응답 캐시 금지", "만료 오류 시 1회 자동 재발급 후 재시도"를 세트로 넣습니다. 중요 포인트는 단일 조치로 끝내지 않는 것입니다. 예전에 TTL만 2배로 늘렸다가 야간 배치 트래픽에서 동일 증상이 재발했고, 결국 다음 날 다시 배포 창을 열어야 했습니다. 장애는 빠르게 봉합하는 것보다, 재현 경로를 끊는 조합을 고르는 게 운영 비용을 줄입니다.
복구 후 체크리스트는 생각보다 단순하지만 반드시 숫자로 남겨야 합니다. 첫째, 만료 오류율이 얼마에서 얼마로 떨어졌는지(예: 19% -> 0.8%). 둘째, 정상 업로드까지 걸린 중간값 지연이 얼마인지. 셋째, 만료 관련 고객 문의가 며칠 내 다시 증가하는지. 넷째, 서명 관련 코드 소유권이 팀 내에서 분산되어 있는지. 마지막 항목이 중요합니다. URL 생성 로직이 백엔드 두 서비스와 배치 워커에 흩어져 있으면, 작은 변경도 시각 기준이 엇갈려 다시 사고가 납니다. 그래서 우리 팀은 서명 생성을 공용 모듈 하나로 통합하고, 배포 전 점검에 "서명 서버 시간 오차 1초 이내" 항목을 추가했습니다. 이 한 줄 덕분에 최근 두 번의 대규모 캠페인 트래픽에서도 만료 장애를 만들지 않았습니다. Presigned URL 문제는 자주 보이는 만큼 가볍게 취급되기 쉽지만, 시간과 캐시를 함께 다루는 운영 절차를 문서화해 두면 온콜 판단 속도가 확실히 빨라집니다.