평일 아침이면 뉴스 요약 자동화가 “오늘의 뉴스 요약 분석이 완료되어 블로그에 업데이트 되었습니다”라는 메일을 보낸다. 오늘도 그 메일이 왔길래 링크를 눌렀는데 404였다.
콘텐츠도, 빌드도 멀쩡했다
먼저 의심한 건 콘텐츠 자체였다. 근데 확인해보니 마크다운 파일은 정상적으로
생성돼서 이미 origin/main에 푸시까지 돼 있었다. 로컬에서 직접 빌드도 돌려봤는데
성공했고, 결과물 안에 오늘 글도 정상적으로 들어 있었다. 콘텐츠도 빌드도 문제가
없다는 뜻이었다.
그런데 실제 사이트엔 없었다
사이트를 직접 열어보니 뉴스 요약 목록 페이지는 며칠 전 글까지만 보이고 있었다. 그런데 같은 레포에 있는 일반 블로그 글 목록은 오늘 올린 글까지 다 떠 있었다 — 같은 사이트, 같은 빌드 파이프라인인데 왜 한쪽만 안 뜨는지 이상했다.
두 커밋을 비교해보니 답이 나왔다. 오늘 아침 커밋이 두 개였는데, 앞쪽 커밋(일반 글)까지는 반영돼 있고, 그 바로 다음 커밋(뉴스 요약 글)만 반영이 안 돼 있었다. 해당 URL을 직접 열어보니 확실해졌다 — 정말로 404였다. 배포가 통째로 멈춘 게 아니라, 딱 그 커밋 하나만 배포가 안 된 상태였다.
원인은 Cloudflare 쪽이었다
Cloudflare 대시보드를 확인해보니 답이 나와 있었다.
Failed: unable to submit build job
빌드 큐에 작업 자체를 못 넣었다는 뜻 — 코드나 콘텐츠 문제가 아니라 Cloudflare 인프라 쪽 일시적인 오류였다. 실제로 배포를 재시도하니 바로 정상적으로 떴다.
근데 ‘완료’ 메일은 왜 왔을까
여기서 끝낼 수도 있었다. 배포 인프라의 일시적인 오류였고, 재시도로 해결됐으니까. 근데 뭔가 이상했다 — 배포가 실패했는데 왜 “완료” 메일은 이미 와 있었을까.
파이프라인 코드(main.py)를 보니 답이 있었다. git push가 끝나면 그냥 5분을 무작정 자고, 그다음엔 묻지도 따지지도 않고
“완료” 메일을 보내고 있었다. 배포가 실제로 성공했는지는 어디서도 확인하지
않았다 — push가 됐다는 것과 페이지가 떴다는 것을 같은 걸로 취급하고 있었던
셈이다. 평소엔 Cloudflare 빌드가 50초 안팎으로 끝나서 5분이면 충분했으니 이
갭이 드러날 일이 없었을 뿐이다.
실제로 확인하도록 고쳤다
“완료”라고 말하려면 실제로 그 상태인지 확인부터 해야 한다는 게 명확해졌다.
그래서 blog.py에 배포 여부를 직접 폴링하는 로직을 추가했다. push 후 105초
— Cloudflare 빌드가 평소 50초 안팎이니 그 정도면 여유롭게 확인할 수 있다 —
기다렸다가 실제 라이브 URL에 요청을 보내 응답이 오는지 확인한다. 안 떠 있으면
빈 커밋으로 한 번 더 push해서 빌드를 다시 걸고, 최대 3번까지 이 과정을
반복한다. 한 번이라도 뜨면 그 즉시 기존 완료 메일을 보내고, 3번 다 실패하면
별도의 실패 메일을 보낸다 — 이건 그룹 전체가 아니라 나한테만 가도록 했다.
매일 뉴스 요약을 받는 사람들한테 배포 오류 알림까지 갈 이유는 없으니까.
새 의존성은 없다. 다른 모듈(crawler.py)에서도 이미 쓰고 있던 표준
라이브러리 하나로 충분했다.
남는 생각
오늘 배포가 실패한 원인 자체는 순전히 운이었다 — Cloudflare 빌드 큐가 우연히
그 순간에 한 번 삐끗한 것뿐, 우리 코드가 잘못한 건 없었다. 근데 그 우연 덕분에
훨씬 오래된 구조적 문제가 드러났다. “완료됐다”는 신호를 보낼 때, 그 신호가
실제로 뭘 확인한 건지 스스로 물어봐야 한다는 것. git push가 성공했다는 건
배포가 성공했다는 증거가 아니었다 — 그냥 다음 단계로 넘어갈 자격이 생겼다는
뜻일 뿐이었는데, 지금까지는 그 둘을 같은 걸로 취급하고 있었다.