Next.js ISR 오래된 데이터 노출 사고 복구 플레이북
💡 Key Takeaways
- ISR 문제는 코드 결함보다 캐시 키와 재검증 경로 불일치에서 더 자주 발생한다.
- 복구는 무효화 범위 최소화와 우선순위 페이지 선별이 핵심이며, 전체 무효화는 마지막 수단이다.
- 사고 종료 후 재발 방지 룰을 릴리스 체크리스트로 고정해야 같은 장애를 줄일 수 있다.
1) 문제 정의: "배포는 끝났는데 화면은 어제 상태"가 되는 순간
월요일 오전 9시 40분, 마케팅 팀이 캠페인 문구를 바꿨는데 랜딩 페이지 일부만 이전 문구로 보인다는 제보가 들어왔다. 내부 어드민에서는 최신 데이터가 확인됐고, API 응답도 정상인데 사용자 화면만 오래된 상태였다. 이때 팀이 가장 먼저 한 일은 "코드 배포 실패"를 의심하는 것이 아니라 "캐시 계층별 최신성"을 분리해서 보는 것이었다. 브라우저 캐시, CDN 캐시, Next.js ISR 캐시, 백엔드 응답 캐시를 각각 체크리스트로 분리하지 않으면 원인 분석이 순식간에 감정 싸움이 된다. 특히 동일 URL이라도 지역 PoP, 기기, 로그인 상태에 따라 캐시 히트가 달라져서 "나는 보이는데 왜 너는 안 보이냐"가 반복된다. 그래서 사고 시작 5분 안에 기준 URL 3개, 기준 계정 2개, 기준 리전 2개를 고정하고, 스크린샷·응답 헤더·생성 시각을 한 스레드에 모으는 절차를 먼저 실행해야 한다. 이 초기 고정점이 없으면 나중에 로그가 있어도 해석이 불가능해진다.
2) 판단 기준과 원인 분리: ISR 문제를 빨리 좁히는 질문 순서
실무에서는 원인을 기술 이름으로 찾지 않고, "어느 계층에서 최신성이 끊겼는가"로 좁혀야 한다. 첫 질문은 "원본 데이터가 갱신됐는가"다. DB row updated_at과 API 응답 타임스탬프가 현재 시각과 맞지 않으면 ISR 이전 문제다. 두 번째는 "재검증 이벤트가 실제로 발생했는가"다. 온디맨드 revalidate 호출 로그, 실패율, 401/403 비율을 본다. 여기서 토큰 만료로 조용히 실패하는 경우가 많다. 세 번째는 "경로가 정확했는가"다. /product/[id]를 무효화해야 하는데 /products/[id]로 호출하면 성공 로그처럼 보여도 실효가 없다. 우리 팀에서 실제로 겪은 사고도 여기에 가까웠다. 신규 라우트 그룹을 도입하며 경로 접두사가 바뀌었는데, 운영 스크립트는 이전 경로를 계속 호출했다. 마지막은 "TTL이 정책과 맞는가"다. 캠페인성 페이지에 revalidate: 3600을 둔 상태에서 즉시 반영을 기대하면 운영 목표와 구현이 충돌한다. 결국 원인 분리는 로그 양이 아니라 질문 순서가 성패를 가른다.
3) 실행 절차: 30분 복구를 위한 단계별 대응
복구 단계는 항상 "관측 강화 -> 국소 무효화 -> 제한적 재배포 -> 전면 조치" 순서로 진행했다. 먼저 기준 URL의 응답 헤더와 생성 시간을 수집해 어떤 캐시가 오래됐는지 확인한다. 다음으로 우선순위 페이지(유입 상위, 결제 직전, 공지성 페이지)만 온디맨드 재검증을 실행한다. 이때 한 번에 전체 경로를 날리면 백엔드가 순간 과부하를 받아 2차 장애가 생길 수 있으니, 2~3분 간격 배치로 나눠 호출했다. 세 번째 단계에서만 필요한 경우 재배포를 수행하고, 빌드 아티팩트 해시와 런타임 환경 변수를 대조해 "새 코드+옛 설정" 조합을 제거한다. 우리 팀은 이 절차로 27분 만에 사용자 제보 재현 케이스를 모두 해소했다. 핵심은 빨리 많이 지우는 게 아니라, 영향도가 큰 화면부터 정상화해서 체감 장애 시간을 줄이는 것이다. 복구 완료 판정도 단순 200 응답이 아니라 "콘텐츠 버전 문자열 일치"로 검증해야 재발을 막을 수 있다.
4) 운영 체크리스트와 주의사항: 사고를 반복하지 않는 마감 작업
장애가 끝난 직후 1시간이 재발 방지의 골든타임이다. 첫째, 재검증 엔드포인트에 인증 실패 알림을 붙여 5분 이상 실패 시 즉시 온콜에 페이지한다. 둘째, 경로 상수 파일을 단일 소스로 두고 앱 라우트와 운영 스크립트가 같은 값을 참조하게 만든다. 셋째, 릴리스 체크리스트에 "캐시 정책 검토" 항목을 추가해 캠페인·긴급 공지는 기본 TTL을 짧게 설정한다. 넷째, 장애 회고에는 기술 원인만 쓰지 말고 의사결정 맥락을 남긴다. 예를 들어 당시 우리는 전체 무효화를 포기하고 상위 20개 URL만 먼저 복구했는데, 그 판단 덕분에 트래픽 급증 구간에서도 API 에러율을 안정적으로 유지했다. 마지막으로 QA 시나리오에 "이전 데이터 잔존" 케이스를 반드시 넣어야 한다. 기능 테스트를 통과해도 캐시 일관성 테스트를 빼면 같은 문제가 조용히 되살아난다. 운영 품질은 코드 한 줄보다, 반복 가능한 복구 루틴을 팀이 공유하느냐에 달려 있다.