기술

테스트 668건이 전부 통과했는데, 마이그레이션 파일은 한 줄도 안 읽고 있었습니다

2026.09.2210분 읽기

사내 제안서 지식검색 도구에 검색 이력 테이블 하나를 추가하는 작업이었습니다. 모델을 고치고, 마이그레이션을 자동 생성하고, 테스트를 돌렸습니다. 668건이 전부 통과했습니다. 여기까지는 평소와 같은 하루였습니다.

앞 편에서는 테스트용 스키마 자동생성 설정이 운영 배포에 실려 테이블이 통째로 지워진 사고를 다뤘습니다. 이번 편은 그 사고의 반대편입니다. 초록불 668개가 그동안 무엇을 보고 있었는지를 열어 봤습니다.


자동 생성된 마이그레이션이 인덱스를 지우라고 했습니다

자동 생성된 마이그레이션 파일을 열어 보니 모델에 없는 인덱스 두 개를 지우라는 줄이 들어 있었습니다. 모델에 없는 것은 맞습니다.

그런데 그 인덱스는 DB 엔진이 외래키를 받치려고 스스로 만든 것이었습니다. 외래키가 살아 있는 채로 그 인덱스를 지우면 MySQL 은 1553 오류로 거부합니다.

사람이 읽어서 그 두 줄은 걷어냈습니다. 제가 그날 직접 쓴 검색 이력 테이블의 되돌리기 코드에 같은 함정이 있었는데 그건 못 봤습니다. 남이 생성한 줄은 의심하고 내가 쓴 줄은 안 의심하는 비대칭이 그대로 나왔습니다.

이 파일이 깨진 채로도 테스트는 전부 초록불이었습니다. 668건 중 마이그레이션 디렉토리를 읽는 테스트가 한 건도 없었기 때문입니다.


테스트는 모델을 보고, 배포는 마이그레이션을 돌립니다

이유는 테스트 공통 설정에 있었습니다. 테스트는 모델 메타데이터로 스키마를 직접 만들어 가벼운 파일 기반 DB 에 붙입니다. 마이그레이션 도구인 Alembic 은 그 경로에 아예 없습니다. 모델이 맞으면 테스트는 통과합니다.

배포는 반대쪽 길로 갑니다. 기동 스크립트가 Alembic 의 `upgrade head` 를 돌려 운영 DB 의 스키마를 바꿉니다. 마이그레이션 파일이 잘못돼 있으면 그 순간에 처음 드러납니다. 초록불과 운영 사이에 아무도 안 읽는 층이 통째로 남아 있었습니다.

스키마의 진실이 모델 코드와 마이그레이션 파일, 두 벌인 셈입니다. 테스트는 그중 하나만 보고 있었고, 배포는 다른 하나를 실행하고 있었습니다. 그 둘이 어긋나면 668건이라는 숫자는 아무것도 보증하지 않습니다.

테스트 668건이 전부 통과했는데, 마이그레이션 파일은 한 줄도 안 읽고 있었습니다 그림 1


전에 한 말이 틀렸습니다

이 층을 따라가다 배포 경로를 직접 읽었습니다. 그러다 제가 전에 「배포가 마이그레이션을 안 돌린다」고 말했던 것이 틀렸다는 것을 확인했습니다. 기동 스크립트가 돌리고 있었습니다. 실패하면 앱을 안 띄웁니다. 푸시하면 스키마가 자동으로 바뀝니다.

이 한 줄이 틀린 채로 남아 있었으면 「마이그레이션이 깨져도 배포까지는 안전하다」는 안심이 계속 굴러갔을 겁니다. 안 돌린다고 믿는 사람은 마이그레이션 파일을 덜 의심합니다. 저도 그랬습니다.


세 갈래 중 실제 DB 왕복을 골랐습니다

「사람 눈으로 매번 거를 일이 아니다」가 결론이었습니다. 검사기를 만들기로 하고 세 갈래를 놓고 봤습니다.

방식

판단

이유

A. 가벼운 인메모리 DB 로 빠르게

기각

지금 668건이 이미 이렇게 하고 있고, 그것이 문제의 원인입니다. 엔진이 만드는 FK 받침 인덱스가 거기에는 없습니다

B. 전진(upgrade)만 검사

기각

되돌리기 코드는 한 번도 실행된 적이 없는 코드였습니다. 뒤에서 보듯 4건 중 3건이 여기서 나왔습니다

C. 빈 실제 DB 에 끝까지 올렸다가 끝까지 내린다

채택

배포가 돌리는 것과 같은 엔진에서 같은 파일을 같은 순서로 돌립니다

검사기는 이렇게 돕니다. 빈 MySQL 8.0 에 `upgrade head` 를 끝까지 돌리고, 다시 `downgrade base` 로 끝까지 내리고, 그 사이에 외래키가 제대로 만들어졌는지 확인합니다. 도커가 있으면 임시 컨테이너를 띄웠다가 지웁니다.

규칙 두 개를 서로 반대 방향으로 잡았습니다. 환경이 없으면 조용히 넘깁니다. 도커가 없는 자리에서 실패로 처리하면 아무 데서도 안 돌게 됩니다.

반대로 접속 주소가 운영 DB 를 가리키면 즉시 실패합니다. `downgrade base` 까지 돌리는 파일이라 여기서는 관대할 수 없습니다.

손을 하나 더 봐야 했습니다. 설정 모듈이 로컬 설정 파일에서만 DB 주소를 읽고 있어서 바깥에서 바꿀 수가 없었습니다.

마이그레이션 환경 모듈에 주소를 인자로 받는 길을 추가했습니다. 기본값은 그대로 둬서 배포 경로는 안 바뀌었고, 현재 리비전 조회로 그것을 확인했습니다.

테스트 668건이 전부 통과했는데, 마이그레이션 파일은 한 줄도 안 읽고 있었습니다 그림 2


첫 실행에서 4건이 나왔습니다

검사기를 처음 돌리자 4건이 걸렸습니다. 넷 다 실제 DB 에서 끝까지 돌려 보기 전에는 드러나지 않는 것들이었습니다.

그날 제가 직접 쓴 검색 이력 테이블 — FK 를 받치는 인덱스를 테이블보다 먼저 지워 1553

전에 만든 테이블 둘 — 같은 모양

대화 테이블 — 제약 이름 자리가 `None`. 자동 생성이 비워 둔 것을 아무도 안 고쳤습니다

첫 줄이 그날 제가 남의 생성물에서 걷어낸 것과 똑같은 함정입니다. 남의 것은 잡고 제 것은 놓쳤다는 판단이 검사기 첫 실행에서 바로 증명됐습니다.

네 번째는 종류가 다릅니다. 그 제약은 DB 엔진이 자동으로 붙인 이름이라 만들어진 순서에 따라 끝 번호가 달라집니다. 이름을 박아 두는 대신 시스템 카탈로그에서 찾아 지우게 고쳤습니다.


모양이 같아도 다 문제는 아니었습니다

처음에는 「인덱스 드롭 뒤 테이블 드롭」이라는 모양으로 잡으려 했습니다. 그런데 같은 모양이 다른 두 테이블에 더 있었고 그 둘은 정상이었습니다. 두 컬럼짜리 복합 인덱스라 FK 컬럼을 덮고 있지 않았습니다.

판정 기준을 「모양」에서 「그 인덱스가 FK 를 덮고 있나」로 바꿨습니다. 모양으로 잡았으면 멀쩡한 두 건이 오탐으로 걸렸을 겁니다. 오탐이 섞인 검사기는 곧 무시됩니다. 무시되는 검사기는 없는 것과 같습니다.

고친 뒤 마이그레이션 22개가 처음으로 끝까지 올라갔다가 끝까지 내려왔습니다. 이 프로젝트가 시작된 뒤 한 번도 없던 일입니다.

테스트 668건이 전부 통과했는데, 마이그레이션 파일은 한 줄도 안 읽고 있었습니다 그림 3


테스트가 실행하는 것과 배포가 실행하는 것의 차이

배포가 실행하는 것과 테스트가 실행하는 것이 다르면, 그 차이가 곧 미검증 영역입니다. 스키마만의 이야기가 아닙니다. 시드 데이터, 기동 스크립트, 설정 병합이 같은 자리에 있습니다.

되돌리는 길도 같은 층에 있습니다. 롤백 코드는 작성 시점에만 쓰이고 실행은 사고 때 처음 됩니다. 왕복을 정기적으로 돌리지 않으면 가장 급할 때 처음 실행되는 코드가 됩니다. 이번 4건 중 3건이 그 자리에서 나왔습니다.

반대로 이 검사가 과잉인 팀도 있습니다. 되돌리기를 아예 안 쓰기로 정한 전진 전용 팀은 이 버그를 만나지 않습니다. 스키마를 마이그레이션 파일 하나로만 정의하는 팀은 두 벌의 진실 자체가 없습니다.

다만 「안 쓴다」가 결정인지 방치인지는 다른 문제입니다. 제 경우는 방치였습니다.


점검 항목

테스트가 스키마를 어느 경로로 만드는지 열어 봤는가 — 모델에서 만들면 마이그레이션은 검증 범위 밖입니다

배포가 실행하는 것 중 테스트가 안 실행하는 것을 적어 봤는가 — 마이그레이션, 시드, 기동 스크립트, 설정 병합

롤백 경로를 마지막으로 실제로 돌려 본 것이 언제인가

검사 장치에 「환경이 없으면 넘어감」과 「위험하면 멈춤」이 둘 다 있는가

검사기가 마지막으로 실제로 돈 시각이 보이는가 — skip 은 통과와 겉모습이 같습니다


아직 절반입니다

검사기를 만든 것과 그것이 배포를 막는 것은 별개의 일입니다. 지금 배포 경로는 테스트를 하나도 안 돌립니다. 검사기를 배포 관문으로 넣을지는 아직 정하지 않았습니다.

그리고 이 글을 쓰는 노트북은 도커가 꺼져 있어 검사가 skip 됩니다. 조용히 넘기면 「안 돌았다」와 「통과했다」가 겉으로 같아집니다. 검사기가 있다는 사실이 「검증되고 있다」는 새 착시를 만들 수 있습니다. 668건이 만들던 착시와 같은 모양입니다.

테스트 수가 늘수록 착시도 같이 커집니다. 스위트가 어느 경로로 상태를 만드는지를 한 번 열어 보는 것이 건수를 세는 것보다 먼저입니다. 저는 이 프로젝트에서 그걸 668건이 쌓일 때까지 안 했습니다.


연작 「초록불이 거짓말한다」

이 글은 다섯 편 중 3편입니다. 테스트가 전부 통과했는데 실제로는 아무것도 지켜주지 않았거나 운영을 부순 사례를 이어서 다룹니다.

#마이그레이션#테스트전략#스키마#배포#외래키#Alembic#MySQL#롤백#자기정정