Home > AI > Infra > 배포 직후 55분, 핫픽스 4연발 — 문제를 네 번 다시 정의한 하루

배포 직후 55분, 핫픽스 4연발 — 문제를 네 번 다시 정의한 하루
AI Claude Code 배포 트러블슈팅

09:41 — 배포하자마자 터졌다

프록시 로테이션 기능을 배포한 직후, 실서버에서 크롤링이 작동하지 않았다. 로컬에서는 잘 되던 기능이 배포 환경에서만 실패하는 전형적인 상황이었다. 이후 55분 동안 커밋 네 개가 연달아 올라갔다 — 문제를 하나 고칠 때마다 다음 문제가 드러나는 패턴이었다.

10:12 — 1차 원인: 토큰이 서버에 없었다

가장 먼저 확인한 건 프록시 API 토큰이었다. GitHub Secrets에 등록된 WEBSHARE_API_TOKEN이 로컬 개발 환경의 .env.example에는 추가돼 있었지만, 실제 배포 서버의 .env에는 반영되지 않고 있었다. 새 환경변수를 코드에서 쓰기 시작했다고 해서 배포 스크립트가 자동으로 서버까지 그 값을 옮겨주지는 않는다는, 흔하지만 놓치기 쉬운 지점이었다.

SSH 배포 스크립트에 토큰을 서버 .env에 주입하는 로직을 추가하고, 배포가 성공했는지 확인하기 위한 검증 단계도 함께 넣었다 — bookings_count > 0이 될 때까지 최대 3분간 헬스체크를 폴링하는 방식이었다.

10:18 — 2차 원인: 서버가 macOS였다

6분 뒤, 다시 실패했다. 방금 추가한 .env 동기화 로직 자체가 문제였다. sed -i "s|...|...|" 같은 GNU sed 문법을 썼는데, 배포 대상 서버가 macOS(BSD sed)라서 이 문법이 그대로 깨졌다. GNU와 BSD의 sed -i 옵션 문법이 다르다는 건 흔히 알려진 함정인데, 실제로 걸려보고 나서야 수정에 들어갔다.

해결은 플랫폼에 안전한 방식으로 바꾸는 것이었다 — sed -i 대신 grep -v "^KEY="로 기존 줄을 제외한 뒤 mv로 덮어쓰는 방식. sed 방언 차이를 아예 우회하는 접근이다.

10:24 — 3차 원인: 예상보다 훨씬 오래 걸렸다

6분 뒤 또 막혔다. 이번엔 토큰도 제대로 들어갔고 동기화 스크립트도 정상 동작했는데, 21일치 데이터를 프록시를 거쳐 크롤링하는 데 10분 이상 걸리고 있었다. 직접 연결보다 프록시 경유가 훨씬 느린 건 당연한데, 기존 헬스체크 대기 시간(3분, 18회×10초)으로는 항상 타임아웃에 걸릴 수밖에 없는 구조였다.

대기 시간을 8분(48회×10초)으로 늘리고, SSH 액션 자체의 타임아웃도 15분으로 넉넉하게 잡았다. 그리고 이번엔 “왜 안 되는지”를 다음에 더 빨리 알 수 있도록, 6회마다 컨테이너 로그(docker logs golf-crawler --tail 5)를 출력하도록 계측을 더했다 — 같은 종류의 문제가 다시 생겼을 때 원인 파악 시간을 줄이기 위한 선제적 조치였다.

10:36 — 문제를 다시 정의했다

12분 뒤, 근본적인 재검토가 있었다. 대기 시간을 늘리는 방식으로는 한계가 있다는 걸 인정한 것이다. 21일치 크롤링 자체가 30분 이상 걸릴 수 있는데, 배포 스크립트의 헬스체크를 아무리 늘려도 이걸 다 기다리게 하는 건 지속 가능하지 않았다.

그래서 접근을 바꿨다. “배포 스크립트가 더 오래 기다리게” 만드는 대신, 애플리케이션이 크롤링 도중에도 일부 데이터를 먼저 보여주도록 구조를 바꾼 것이다. 크롤링 첫째 날 분량이 완료되는 시점, 그리고 이후 3일마다 캐시를 점진적으로 갱신하도록 크롤링 서비스를 수정했다.

최종적으로 무엇이 바뀌었나

배포 헬스체크의 기준 자체가 “크롤링이 완전히 끝났는가”에서 “일부 데이터라도 나왔는가”로 낮아졌다. 대기 시간을 3분에서 8분으로 늘린 것(3차 수정)은 여전히 남아있지만, 진짜 해결은 크롤링을 점진적으로 노출하도록 애플리케이션 구조를 바꾼 것(4차 수정)이었다.

시각 무엇을 고쳤나 다음에 드러난 문제
09:41 프록시 로테이션 기능 배포 서버에서 크롤링 실패
10:12 배포 스크립트에 토큰 동기화 추가 macOS sed 문법 오류
10:18 sed 대신 grep+mv 방식으로 변경 헬스체크 타임아웃
10:24 대기 8분 확장 + 로그 계측 추가 대기 자체가 근본 해법이 아님을 재확인
10:36 점진적 캐시 갱신으로 구조 변경 (해결)

남는 교훈

이 55분을 되짚어보면, 매번 “고쳤다”고 생각한 지점이 실은 다음 문제의 전제 조건을 충족시켰을 뿐이었다. 토큰이 있어야 sed 문제가 드러났고, sed가 고쳐져야 타임아웃 문제가 드러났고, 타임아웃을 늘려봐야 그게 임시방편이라는 게 드러났다.

같은 기능 하나가 배포 파이프라인의 여러 층 — 시크릿 동기화, 플랫폼 차이, 타임아웃 예산, 애플리케이션 구조 — 을 순서대로 건드리는 경우가 있다. 이럴 때는 첫 번째 실패에서 바로 “근본 원인”을 찾으려 하기보다, 매 실패마다 계측을 조금씩 더해가며(이번엔 컨테이너 로그 출력을 추가한 것처럼) 다음 원인을 더 빨리 좁힐 수 있게 만드는 것도 하나의 전략이었다.