슬러그 문제를 고치고 다시 빌드했습니다. 159편 중 157편은 정상이고 2편만 404였습니다. 이상한 숫자였습니다. 데이터가 잘못됐다면 특정 채널이나 특정 조건이 통째로 깨져야지, 무작위로 2개만 깨질 이유가 없었습니다.
깨진 2편을 확인하다 등골이 서늘해졌습니다. 그 2편은 정확히 제가 조금 전에 확인해 본 바로 그 2편이었습니다.
일어난 일
순서대로 적으면 이렇습니다.
- 고치기 전 버전으로 개발 서버를 띄워 두고 있었습니다. 그때
/blog/1과/blog/164를 열어 봤고, 당연히 404가 났습니다. - 코드를 고치고 서버를 종료했습니다. 종료했다고 믿었습니다.
- 다시 빌드하고 서버를 띄웠습니다.
- 확인해 보니 그 2개만 여전히 404였습니다.
실제로는 2번에서 프로세스가 죽지 않았습니다. Git Bash에서 pkill -f "next start"를 썼는데, 이게 Windows 네이티브 프로세스를 잡지 못했습니다. 살아 있는 서버가 포트를 물고 있으니 3번에서 띄운 새 서버는 포트 충돌로 조용히 실패했고, 제 요청에 응답한 것은 옛 빌드를 쥔 좀비 서버였습니다.
결정적인 부분은 여기입니다. 그 좀비가 요청을 처리하면서 404 결과를 새로 만든 빌드 디렉터리에 캐시 파일로 기록했습니다. 그래서 이후에 제대로 된 서버를 띄워도, 그 2개는 캐시된 404를 그대로 돌려줬습니다. 빌드 산출물이 오염된 것입니다.
"내가 열어 본 페이지만 깨진다"는 증상은 이렇게 만들어졌습니다.
확실하게 죽이는 법
Windows에서는 포트를 기준으로 잡는 게 확실합니다.
Get-NetTCPConnection -LocalPort 3000 |
Select-Object -ExpandProperty OwningProcess |
ForEach-Object { Stop-Process -Id $_ -Force }
그리고 새 서버가 정말 새로 떴는지를 로그로 확인합니다. 기동 로그(Ready in ...)가 이번 실행에서 새로 찍혔는지 눈으로 봅니다. 이 한 줄을 확인하지 않은 것이 전부의 원인이었습니다.
오염이 의심되면 빌드 디렉터리를 지우고 다시 빌드합니다. 캐시가 남아 있는 한 코드를 아무리 고쳐도 같은 답이 나옵니다.
일반화
이 사고의 핵심은 Windows도 Next.js도 아닙니다. 검증 도구가 검증 대상을 오염시킨 것입니다.
증상이 설명되지 않는 모양일 때 — 무작위로 보이는데 자세히 보면 무작위가 아닐 때 — 데이터를 더 파기 전에 관측 환경 자체를 의심하는 게 빠릅니다. 저는 "깨진 2개가 하필 내가 열어 본 2개"라는 걸 알아차리고서야 방향을 틀었습니다. 그 전까지는 데이터를 뒤지고 있었습니다.