본문으로 건너뛰기
2026. 6. 29·약 4분

DB 스키마에도 Git이 필요할까요?

한 줄 위에 데이터베이스 원통 셋이 놓이고 맨 앞 원통에만 깃발이 꽂힌 그림

컬럼 하나를 추가하려고 보니 ALTER TABLE을 어디에 적어야 하는지부터 막혔습니다. DB 콘솔에서 직접 실행하는 게 아니라 SQL 파일을 레포에 커밋한다는 것, 그 파일들이 순서대로 쌓여서 지금의 스키마를 만든다는 것, 그러니까 스키마에 git이 하나 더 있다는 것을 그때 알았습니다. 그리고 그 git의 규칙을 어겨서 CI를 한 번 깨뜨려 봤습니다.

살아 있는 데이터 위에서 스키마를 바꾸는 일은 어떻게 관리될까요?


이 글에서 다루는 내용​

기존 데이터를 유지하면서 DB 스키마를 바꾸는 방법을 다룹니다. 마이그레이션의 변경 목록(changelog)과 초기화용 스냅샷(baseline)을 구분하고, 둘을 함께 고쳤다가 CI가 실패한 사례를 봅니다. 이어서 데이터의 변경 이력을 남기는 audit 테이블을 살펴봅니다.





기존 데이터를 유지하며 구조 바꾸기​

제가 주로 다루던 화면의 메모리 상태는 새로고침하면 다시 만들 수 있었습니다. 서버의 DB는 기존 데이터를 유지한 채 구조를 바꿔야 합니다. 브라우저에서도 IndexedDB처럼 데이터를 오래 보관한다면 마이그레이션이 필요합니다. 여기서는 여러 서버와 사용자가 공유하는 DB를 바꾸는 상황에 집중하겠습니다.

컬럼을 추가하거나 자료형을 바꿀 때는 이미 저장된 값을 새 코드가 어떻게 읽을지도 정해야 합니다. 이 변경은 개발·스테이징·프로덕션과 동료의 로컬 DB에서 같은 순서로 적용되어야 합니다.

이를 위해 변경을 파일로 기록하고 각 DB에 어디까지 적용했는지 추적하는 마이그레이션 도구를 씁니다.




changelog는 커밋, baseline은 스쿼시​

Liquibase나 Flyway 같은 도구는 DB 변경 파일과 적용 이력을 관리합니다. 제가 작업한 레포에서는 변경 목록과 초기화용 스냅샷을 함께 사용했습니다. git의 커밋 이력과 스쿼시를 떠올리면 두 역할을 구분하기 쉽습니다.

  • changelog(체인지셋): 증분 변경 SQL입니다. 파일로 레포에 커밋되고, 도구가 순서대로 적용하며, 이미 적용된 것은 건너뜁니다. git의 커밋 히스토리입니다.
  • baseline: 특정 시점의 스키마 전체 스냅샷입니다. 새 환경을 처음부터 체인지셋 수백 개로 쌓지 않고 빠르게 초기화하는 용도입니다. 스쿼시된 초기 커밋입니다.

changelog는 커밋 히스토리, baseline은 스쿼시된 스냅샷

컬럼 추가는 그래서 DB 콘솔이 아니라 코드 리뷰를 받는 SQL 파일이 됩니다.

-- changelog: 증분 변경만 쌓는다. 순서대로 적용되고, 적용된 것은 건너뛴다
ALTER TABLE profile ADD COLUMN military_service JSON NULL;

서두의 CI 실패는 이 두 파일을 함께 고쳐서 생겼습니다. 새 컬럼을 changelog에 적고 baseline 스냅샷에도 추가했습니다. 테스트는 baseline으로 DB를 초기화한 뒤 changelog를 적용했으므로, 같은 컬럼을 두 번 추가하다 Duplicate column 에러가 났습니다. 이 레포의 규칙은 신규 변경을 changelog에만 적고 baseline 갱신은 도구에 맡기는 것이었습니다.




변경 이력은 자동으로 남깁니다​

스키마의 변경 이력이 changelog에 남는다면, 데이터의 변경 이력은 어디에 남을까요. 개인정보나 급여처럼 민감한 도메인에는 "누가 언제 무엇을 바꿨는가"를 답해야 하는 감사(audit) 요구가 따릅니다.

이걸 매번 손으로 기록하는 대신 자동화하는 장치가 있습니다. JPA 진영에서는 Hibernate Envers가 대표적입니다. Envers를 설정하고 엔티티에 @Audited를 붙이면 ORM을 통해 수행한 변경의 이력이 감사 테이블에 쌓입니다. 기본 접미사는 _AUD이며, 이 레포처럼 _audit으로 바꿀 수도 있습니다. 직접 실행한 SQL이나 벌크 DML까지 모두 추적하는 것은 아닙니다.

@Audited // 변경분이 profile_audit 테이블에 자동 적재된다: 누가, 언제, 무엇을
@Entity
class ProfileEntity(/* ... */)

클래스에 이미 @Audited가 붙어 있다면 감사 대상 필드를 추가할 때 audit 테이블도 함께 바꿔야 합니다. 기본 리비전 정보는 번호와 시각이며, 누가 바꿨는지 기록하려면 사용자 정보를 담는 리비전 엔티티와 리스너를 별도로 구성해야 합니다. 프론트엔드의 상태 변경 이력과 비슷하지만 목적은 되돌리기가 아니라 변경 경위를 조회하는 데 있습니다. audit 테이블에서 변경자·시각·값을 확인할 수 있습니다.

다음 글에서는 마이그레이션 도구를 포함한 백엔드 빌드·검증 도구를 정리합니다.




FE ↔ BE 대응표​

이 글에서 다룬 대응입니다.

BE 개념FE에서 가장 가까운 것대응의 핵심
스키마 마이그레이션IndexedDB 버전 업그레이드살아남는 데이터를 새 구조로 옮기는 일
changelog / baseline커밋 히스토리 / 스쿼시DB용 git
audit 테이블 (Envers)undo/history 로그되돌리기가 아니라 답하기 위한 이력



References​

진화하는 데이터베이스

  1. Martin Fowler, Pramod Sadalage: Evolutionary Database Design

도구

  1. Liquibase Documentation: Concepts
  2. Hibernate Envers: Documentation


좋은 사람들과 재미있는 일을 하며 열정적이고 즐겁게 살고 싶은 개발자