대니
danny's blog@dannywon_dev
발행 97 · 대기 162

1인 개발자. 클로드 코드를 팀처럼 굴린다. 매일 겪은 것만 쓴다.

클로드 코드

마이그레이션 검사가 통과했는데 함수가 깨진 이유

칸 이름을 바꾸고 검사는 통과했다. 그림자 칸을 걷자 함수 여덟 개가 깨졌다.

칸 이름을 바꾸는 마이그레이션 끝에 검사를 붙여 뒀는데 「통과」가 나왔다면, 그 검사가 언제 돌았는지 한번 보는 게 좋다.

2026-08-12 밤. 데이터베이스 칸 이름 13개를 바꿨다. fair_idexhibition_id 같은 식이다.

칸 이름을 바꾸면 배포되는 몇 분 동안 지금 돌고 있는 코드가 죽는다. 옛 이름으로 읽고 쓰는데 그 이름이 없어지니까. 그래서 다리를 놓았다.

  1. 진짜 칸을 새 이름으로 바꾼다
  2. 옛 이름으로 「그림자 칸」을 하나 더 만든다
  3. 트리거가 둘을 맞춘다 — 어느 쪽에 써도 반대쪽이 따라온다
  4. 코드를 배포하고, 다 끝나면 그림자를 걷는다

그리고 마이그레이션 끝에 검사를 붙였다. 데이터베이스 함수를 전부 다시 만들어 보는 검사다. 함수 본문이 없는 칸을 가리키면 그 자리에서 실패한다. 글자를 훑는 것보다 훨씬 세다.

검사는 통과라고 답했다.

그림자를 걷고 나서, 함수를 실제로 불러봤다.

column h.fair does not exist

함수 여덟 개가 깨져 있었다.

다리가 검사까지 건너게 해 줬다

처음엔 「치환 목록에서 칸 하나를 빠뜨렸다」로 봤다. 맞다 — fair_id 는 목록에 넣었는데 fair 라는 칸을 안 넣었다.

그런데 그건 왜 검사에 안 걸렸나. 여기가 진짜다.

검사가 돈 시점에는 그림자 칸이 아직 살아 있었다. 함수가 h.fair 라고 쓰고 있었고, 그림자 이름이 정확히 fair 였다. 그러니 함수는 멀쩡히 만들어졌고 검사는 통과했다.

갈린 건 **「검사를 어디에 두느냐」**였다. 바꾸는 마이그레이션 안에 두면 같이 커밋되고 같이 읽혀서 관리가 편하다. 대신 그 시점엔 옛 이름이 아직 유효하다.

편한 쪽이 틀렸다. 검사는 「바꾼 직후」가 아니라 「옛것을 치운 직후」에 있어야 한다. 그 전에는 무엇을 물어도 답이 「통과」로 나온다.

뭘 바꿨나

하나 — 검사를 철거 마이그레이션으로 옮겼다. 그림자 칸을 지우는 파일 맨 끝에서 함수를 전부 다시 만들어 본다. 거기서 처음으로 진짜 답이 나온다.

둘 — 기계가 못 잡는 구간을 인정하고 손으로 메웠다. 절차형 함수(plpgsql)는 만들 때 본문 검사를 안 받는다. 다시 만들어 보는 검사로도 안 걸린다. 그래서 함수를 트랜잭션 안에서 실제로 호출하고 되돌리는 스크립트를 따로 돌린다. 그날 깨진 여덟 개 중 넷이 그렇게 잡혔다.

곁가지 — 같은 다리가 배포를 살렸다

같은 날, 마이그레이션이 다른 이유로 두 번 중간에 멈췄다. 앞부분은 이미 적용된 상태였다.

그런데 서비스는 안 끊겼다. 옛 이름 다리가 받쳐 주고 있었기 때문이다.

다리를 놓는 건 배포 몇 분을 건너려고 하는 일인데, 실패한 배포를 살리는 데도 값을 했다. 같은 장치가 한쪽에서는 눈을 가리고 다른 쪽에서는 목숨을 구했다. 장치가 좋고 나쁜 게 아니라, 그게 켜져 있는 동안 무엇이 참인지를 아는 게 일이다.

← 목록으로