← 모든 글

Migration Script, 한 번 더 돌려도 안전한가

멱등성 체크리스트 — 두 번 돌려서 망한 경험을 정리했다.

두 번 돌려서 망한 기억

롤백 직후 같은 migration을 재실행했다. INSERT 가 중복 PK 에러를 냈고, 그걸 무시하도록 --force 를 붙였더니 데이터 일부가 덮였다. 총 피해 복구에 3시간. 원인은 단순했다 — script 를 “한 번만 돌릴 것” 전제로 짰다.

막상 운영에서는 다시 돌려야 하는 상황이 생각보다 잦다. 중간 실패, 부분 롤백, 환경 재현, QA 반복 실행. 그래서 지금은 script 를 짤 때 첫 줄부터 멱등성 전제로 시작한다.


체크리스트

테이블/컬럼 DDL

  • CREATE TABLE IF NOT EXISTS — 기본.
  • ADD COLUMN 은 존재 여부를 먼저 확인. PostgreSQL 기준:
    ALTER TABLE foo ADD COLUMN IF NOT EXISTS bar TEXT;
  • index 생성은 CREATE INDEX IF NOT EXISTS.

데이터 DML

  • INSERT ... ON CONFLICT DO NOTHING 또는 ON CONFLICT DO UPDATE.
    • DO NOTHING: 기존 행 건드리지 않아야 할 때.
    • DO UPDATE: 실행할 때마다 최신 상태로 수렴해야 할 때.
  • UPDATE 는 대부분 멱등 — 단, 값을 누적하는 경우(count + 1) 는 절대 안전하지 않다. 플래그로 guard 걸 것.
  • DELETEINSERT 조합은 위험. 가능하면 upsert 로 교체.

실행 상태 추적

  • schema_migrations 같은 테이블에 script 이름 + 실행 완료 timestamp 를 남긴다.
  • script 진입부에서 이미 완료된 기록이 있으면 early exit.
  • 직접 구현하기 귀찮으면 Flyway / Liquibase 가 이걸 기본으로 해준다. 단, 이미 커스텀 runner 가 있다면 굳이 교체 안 해도 된다.

부수 효과 처리

  • 이메일 발송, 외부 API 호출이 migration 중에 끼어 있다면 별도 job 으로 분리. script 와 묶으면 재실행 때 중복 발송 막기가 훨씬 복잡해진다.
  • S3 업로드, 파일 생성 등도 마찬가지. 존재 확인 후 skip 로직 필수.

검증

  • 로컬에서 같은 script 를 연속 두 번 돌린다. 에러 없고 row count 동일하면 통과.
  • CI 파이프라인에 migrate && migrate 를 넣어두면 리뷰 없이 잡힌다.

다음 한 가지

이번 sprint 안에 CI에 migrate && migrate 이중 실행 스텝 하나 추가한다.


🛒 이 글과 어울리는 추천 상품

위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.