이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Schema migration은 왜 코드 배포와 같은 수준으로 관리해야 하는가?
- Migration 파일을 작성할 때 어떤 원칙을 지켜야 하는가?
- 개인 프로젝트에서 Flyway/Liquibase 같은 도구가 왜 도움이 되는가?
개요
Schema migration은 DB 구조 변경의 이력이다. 애플리케이션 코드가 Git으로 관리되듯, DB 스키마 변경도 버전 관리되어야 한다. 운영 DB에 직접 ALTER TABLE을 실행하고 기억에 의존하면 환경 간 차이와 복구 실패가 생긴다.
백엔드 개발자는 migration을 “테이블 만드는 SQL”이 아니라 배포 가능한 변경 단위로 봐야 한다.
원리
Migration 도구는 보통 적용된 migration 버전을 DB에 기록한다.
V1__create_members.sql
V2__create_orders.sql
V3__add_member_status.sql각 환경은 아직 적용되지 않은 migration을 순서대로 실행한다. 이렇게 하면 local, dev, staging, prod의 스키마 이력을 맞출 수 있다.
도구가 기록하는 것은 “어떤 migration이 적용되었는가”다. “그 migration이 운영에 안전한가”까지 보장하지는 않는다. 대형 테이블 DDL, 긴 DML, destructive change는 별도 설계가 필요하다.
좋은 Migration
좋은 migration은 작고, 되도록 되돌릴 수 있으며, 의도가 분명하다.
-- V3__add_member_status.sql
ALTER TABLE members
ADD COLUMN status varchar(20);
UPDATE members
SET status = 'ACTIVE'
WHERE status IS NULL;
ALTER TABLE members
ALTER COLUMN status SET NOT NULL;대형 테이블에서는 이 한 파일도 위험할 수 있다. nullable 컬럼 추가, backfill, not null 추가를 여러 단계로 나누는 편이 안전할 수 있다.
운영에서는 다음처럼 배포 단위를 나눈다.
1. nullable status 컬럼 추가
2. 애플리케이션이 status가 null이어도 동작하도록 배포
3. 작은 배치로 backfill
4. status not null 검증 쿼리 수행
5. NOT NULL 제약 추가
6. 오래된 호환 코드 제거한 파일에 모두 넣으면 local에서는 깔끔하지만 prod에서는 중단, 재시도, 검증 지점이 사라진다.
위험한 Migration
다음은 운영에서 위험하다.
ALTER TABLE orders
ALTER COLUMN total_price TYPE numeric(19, 2);대형 테이블에서 컬럼 타입 변경은 테이블 rewrite와 긴 lock을 만들 수 있다. DB 버전과 엔진별 동작을 확인해야 한다.
또한 destructive change는 단계적으로 해야 한다.
ALTER TABLE members DROP COLUMN name;구버전 애플리케이션이 아직 name을 읽고 있으면 장애가 난다.
삭제 migration은 “코드에서 더 이상 안 쓴다”만으로 충분하지 않다. batch, admin, report, BI, 데이터 추출 작업, 수동 운영 쿼리까지 확인해야 한다. 기업 환경에서는 컬럼 하나가 예상보다 넓은 조직 경계에서 쓰이는 경우가 많다.
Migration 작성 원칙
실무에서는 다음 기준을 checklist로 둔다.
- 이미 적용된 versioned migration은 수정하지 않는다.
- DDL과 대량 DML은 되도록 분리한다.
- 대형 테이블 변경은 staging 또는 prod-like 데이터에서 시간을 측정한다.
- destructive change는 최소 두 번 이상의 배포 뒤 실행한다.
- migration이 실패했을 때 재실행 가능한지 확인한다.
- lock timeout, statement timeout, maintenance window를 정한다.
- 검증 SQL을 migration PR에 포함한다.
PostgreSQL에서는 운영 DDL 앞에 세션 단위 timeout을 두는 팀도 많다.
SET lock_timeout = '3s';
SET statement_timeout = '5min';이 설정은 만능 안전장치가 아니다. 다만 예상보다 긴 lock을 잡고 서비스 전체를 막는 일을 줄이는 데 도움이 된다.
개인 프로젝트 기준
개인 프로젝트에서도 migration 도구를 쓰면 좋다.
- 스키마 변경 이력이 남는다.
- 새 환경을 쉽게 만들 수 있다.
- 포트폴리오에서 운영 감각이 보인다.
- 팀 프로젝트에서 충돌을 줄인다.
- DB 구조가 코드 리뷰 대상이 된다.
Spring Boot에서는 Flyway나 Liquibase를 자주 사용한다.
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration개인 프로젝트에서 수준을 높이고 싶다면 다음까지 해본다.
- local container DB에 migration으로 schema 생성
- 테스트 실행 시 migration 적용
- README에 migration 실행 방법 기록
- staging 배포에서 migration 로그 확인
- 위험한 변경은 issue나 PR에 runbook 작성
실전 팁
- migration은 작게 나누고 PR에서 리뷰한다.
- DDL과 대량 DML을 한 파일에 섞지 않는다.
- 운영 대형 테이블 변경은 lock 영향을 확인한다.
- destructive change는 최소 두 번 이상의 배포로 나눈다.
- migration 실패 시 재실행 가능성을 고려한다.
- Flyway/Liquibase는 이력 관리 도구이고, 운영 lock과 데이터량 위험은 별도 검토한다.
- migration PR에는 변경 의도, 영향 테이블, 예상 row 수, 검증 SQL을 함께 적는다.
위험 신호!
- 운영 DB에서 수동으로 스키마를 바꾼 뒤 파일을 나중에 맞춘다.
- local과 prod 스키마가 다르다.
- migration 파일이 이미 적용된 뒤 수정된다.
- 대량 update가 migration에 포함되어 배포 시간을 예측할 수 없다.
- rollback 전략 없이 drop column을 한다.
- migration 도구를 쓰고 있다는 이유로 운영 DDL lock 검토를 생략한다.
- 적용된 migration 파일을 수정해 checksum을 억지로 맞춘다.
- batch나 admin이 쓰는 컬럼을 확인하지 않고 삭제한다.
확인 질문
- Schema migration을 버전 관리해야 하는 이유는 무엇인가?
- 환경 간 스키마를 일관되게 유지하고, 변경 이력과 배포 순서를 추적하며, 복구와 재현을 가능하게 하기 위해서다.
- 이미 운영에 적용된 migration 파일을 수정하면 위험한 이유는 무엇인가?
- 환경마다 적용 이력이 달라져 checksum 오류나 스키마 불일치가 생길 수 있기 때문이다.
- 개인 프로젝트에서 migration 도구가 주는 장점은 무엇인가?
- DB 변경 이력을 남기고 새 환경을 쉽게 재현하며 운영 감각을 보여줄 수 있다.
- 도구를 써도 위험한 migration 검토가 필요한 이유는 무엇인가?
- Flyway나 Liquibase는 적용 순서와 이력을 관리하지만 DDL lock, table rewrite, 대량 DML 부하를 자동으로 없애지는 않기 때문이다.