배포 확인을 하루 세 번 했는데, 셋 다 아무것도 확인하지 않았습니다
운영 서버의 사용량 집계 응답을 새로고침하며 4분을 기다렸습니다. 새 칸 둘이 생기면 문서 엔진 배포가 끝난 것으로 읽을 생각이었습니다. 응답은 계속 빈 객체였습니다. 「아직 안 됐다」로 읽고 다시 새로고침했습니다.
그런데 그 서버에는 집계할 행이 하나도 없었습니다. 행이 없으면 합계는 빈 객체이고, 새 칸이 나올 자리 자체가 없습니다. 배포가 됐든 안 됐든 그 응답은 똑같이 보였습니다. 4분 동안 본 것은 배포 상태가 아니라 데이터가 없다는 사실이었습니다.
그날 이런 식으로 틀린 것이 세 번째였습니다. 앞의 둘은 한 번은 실수로, 한 번은 우연으로 넘겼습니다. 세 번째에서야 셋의 모양이 같다는 것이 보였습니다.
세 번, 같은 모양으로 틀렸다
배경은 사내 제안서 지식검색 도구의 배포 관문을 고치는 작업이었습니다. 관문이 옛 프로세스의 200 응답을 보고 통과시켜, API 가 import 단계에서 죽은 배포가 CI 에서 성공으로 끝난 사고가 있었습니다. 그 원인을 잡는 날이었습니다.
하루에 여러 번 배포하는 작은 환경이라 고치는 중간중간 「됐나 안 됐나」를 볼 일이 계속 생겼습니다. 그 확인이 세 번 다 틀렸습니다.
첫 번째는 테스트 격리였습니다. 관문을 돌려볼 수 있게 셸 테스트를 붙이면서, 테스트가 작업 트리에 산출물을 남기지 않는지 git status 로 봤습니다. 깨끗했습니다.
그런데 그 산출 경로는 방금 .gitignore 에 넣은 것이었습니다. 남았든 안 남았든 git status 는 비어 있습니다. 안 보이는 것이 당연한 명령으로 「안 남았다」를 읽었습니다. 테스트가 밖으로 새는 것을 가볍게 넘길 수 없는 이유는 전에 한 번 치렀습니다.
두 번째는 화면 배포였습니다. 사용량 화면에 새 탭이 올라갔는지 보려고 서버에서 페이지를 받아 문자열을 찾았습니다. 없었습니다. 그런데 그 화면은 로그인 뒤에서 브라우저가 그립니다. 서버가 내려주는 HTML 에는 배포 전이든 후든 그 문자열이 없습니다.
세 번째가 첫 문단의 4분입니다. 집계 응답의 새 칸으로 문서 엔진 배포를 보려 했는데, 집계할 행이 0건이라 칸이 나올 수 없는 환경이었습니다.
확인하려던 것 | 고른 신호 | 「있다」가 나올 수 없는 이유 |
|---|---|---|
테스트 격리 | git status 가 깨끗한가 | 산출 경로가 무시 목록에 있다 |
화면 배포 | 서버 HTML 에 문자열이 있나 | 로그인 뒤에서 브라우저가 그린다 |
엔진 배포 | 집계 응답에 새 칸이 있나 | 집계할 행이 0건이다 |

「없다」를 증명할 수 없는 신호였다
셋의 공통점은 하나입니다. 데이터나 상태에 기대는 신호를 골랐습니다. 그 신호가 「있다」로 바뀔 조건이 이 환경에는 없었습니다. 그래서 「아직」과 「아님」이 같은 모습으로 보였습니다.
잠긴 우편함 앞에서 「편지가 안 왔다」고 말하는 것과 같습니다. 우편함이 잠겨 있으면 편지가 왔어도 안 보이고, 안 왔어도 안 보입니다. 안을 못 보는 우편함은 편지의 유무를 알려주는 물건이 아닙니다. 확인이 되려면 우편함을 먼저 열어야 하고, 그 뒤에야 「비었다」가 뜻을 가집니다.
대안이 없었던 것이 아닙니다. 셋 다 다른 신호가 있었습니다.
격리: 무시 목록을 잠시 빼고 보거나, 산출 디렉토리를 직접 ls 한다
화면: 빌드 산출물의 청크에서 문자열을 찾거나, 로그인한 브라우저에서 본다
엔진: 응답이 아니라 코드를 본다. 커밋 해시·파일 수정 시각·프로세스 기동 시각
신호 자체가 나쁜 것은 아닙니다. 데이터가 이미 쌓인 환경이라면 새 칸은 곧 배포 증거입니다. 「이 환경에서 그 신호가 나올 수 있나」를 먼저 묻지 않은 것이 문제였습니다.
그날 고치던 버그도 같은 병이었다
세 번째에서 공통 모양이 보이자, 그날 고치던 관문 버그가 네 번째 사례로 읽혔습니다. 배포 스크립트가 재시작을 떼어 띄우고 곧바로 헬스체크를 쳤습니다. 옛 프로세스가 아직 살아 있어 200 을 줬습니다. 관문 통과까지 182ms, 새 프로세스 기동은 그보다 1초 뒤였습니다.
이 검사는 「무엇이든 응답하나」를 묻고 있었습니다. 실패할 수 없는 검사입니다. 그래서 프로세스 생존 확인·상태코드·화면 2xx 라는 세 번의 강화를 다 견디고 살아남았습니다. 셋 다 맞는 수정이었는데, 검사 자체가 옛 것을 보고 있었습니다.
200 은 누가 줬는지 말하지 않습니다. 옛 프로세스·캐시·기본값이 답한 200 은 새 코드의 200 과 겉모습이 같습니다. 에러 로그가 0건인데 고장이던 사례도 같은 자리에 있습니다.
관문은 「새 프로세스가 응답하나」로 바꿨습니다. 재시작 전에 포트를 듣는 pid 를 찍어 두고, 그 값이 바뀐 뒤에 건강을 묻습니다. 고친 뒤 실측은 API 관문 182ms → 4.2초, 화면 관문 57ms → 1.1초였습니다. 관문이 처음으로 실제로 기다렸습니다.
그리고 관문을 돌려볼 수 있게 만들었습니다. 옛 판에 같은 테스트를 돌려 세 시나리오가 실제로 실패하는지 봤습니다. 옛 판 7/10, 새 판 10/10 이었습니다. 옛 코드에서도 통과하는 테스트는 아무것도 증명하지 않습니다. 이것도 같은 규칙입니다.

배포 확인은 「코드가 거기 있나」로 본다
응답 내용은 데이터가 있어야 달라집니다. 그래서 배포 확인의 신호로는 못 씁니다. 이날부터는 운영 서버에 들어가 다섯 가지를 봅니다.
체크아웃된 커밋 해시가 푸시한 것과 같은가
배포 실패 마커 파일이 없는가
바뀐 파일의 수정 시각이 푸시 뒤인가
프로세스 기동 시각이 커밋보다 뒤인가. 이날은 커밋 10:11:46, 재기동 10:12:52 였다
바뀐 모듈을 그 환경에서 직접 import 해 본다
마지막 줄이 특히 중요합니다. 늦게 import 되는 모듈은 기동 때 불리지 않습니다. 색인 파이프라인이 그렇습니다. 헬스체크가 초록불이어도 밤 배치가 그 모듈을 처음 부르는 순간 터집니다.
「코드가 거기 있나」는 필요조건이지 충분조건이 아닙니다. 커밋 해시와 기동 시각이 맞아도 설정이 옛 것이거나 캐시가 살아 있으면 동작은 그대로입니다. 그래서 마지막에 직접 import 를 넣었고, 그것도 기동 경로까지만 봅니다.
모든 팀이 운영 서버에 ssh 를 열 수 있는 것은 아닙니다. 서버 접근이 없으면 배포 파이프라인이 커밋 해시와 기동 시각을 상태 엔드포인트나 응답 헤더로 내보내 줘야 같은 확인이 됩니다. 그것 없이 「응답이 바뀌었나」만 보는 환경은 이 글의 함정을 구조적으로 안고 있습니다.

확인 수단을 고르기 전에 묻는 것
확인 수단을 고르기 전에 「이게 아니라면 무엇이 달라 보일까」를 답한다. 답이 「똑같다」면 그 수단을 버린다
「없다」를 확인하려면 「있다」가 보일 조건을 먼저 만든다. 0건 집계·무시 목록·로그인 뒤 화면은 「있다」가 나올 수 없는 자리다
배포·빌드·배치처럼 비동기로 진행되는 일은 내용이 아니라 변화값을 잡는다. 커밋 해시·기동 시각·마커 파일이 그것이다
검사가 통과하면 「무엇이 답했나」를 묻는다. 옛 프로세스·캐시·기본값이 답한 성공은 새 코드의 성공과 겉모습이 같다
같은 실수가 세 번 나오면 실수가 아니라 습관이다. 세 번째에 공통 병을 찾는다
마무리
이날 관문 수정은 단위 테스트와 옛 판 대조로만 확인했습니다. 진짜 깨진 배포를 운영에서 막는 것은 아직 보지 못했습니다. 다음 실패가 첫 실전입니다.
지금은 확인 수단을 고르기 전에 한 줄을 먼저 답합니다. 이게 아니라면 무엇이 달라 보였을까. 답이 「똑같이 보인다」면 그 확인은 아직 아무것도 확인하지 않은 것이라, 다른 수단을 찾습니다.
연작 「실패할 수 없는 검사」
이 글은 다섯 편 중 2편입니다. 초록불·성공 표시·통과한 테스트가 실은 아무것도 검사하지 않던 사례를 이어서 다룹니다.