Featherlight Migrations: Tern 데이터베이스 도구 가이드
저자: Manni Wood
2025년 8월 29일
tern은 시간이 지남에 따라 데이터베이스 스키마 변경을 다루는 데 유용한, 놀라울 정도로 가벼운 도구입니다.
가장 좋은 방법은 예시로 보여주는 것이겠죠.
먼저, 용어에 대해 잠깐 짚고 넘어가겠습니다. 어떤 문맥에서 “데이터베이스 마이그레이션”은 오라클에서 포스트그레스로 옮기는 것처럼 한 데이터베이스에서 다른 데이터베이스로 전환하는 것을 의미하기도 합니다. 하지만 여기서 “데이터베이스 마이그레이션”은 단순히 기존 데이터베이스 스키마에 변화를 주는 것을 뜻합니다.
준비 단계
새로운 프로젝트를 시작한다고 가정해봅시다.
이 프로젝트에는 마이그레이션 파일을 보관할 디렉터리가 필요합니다.
$ mkdir migrations
프로젝트는 비어 있는 데이터베이스에서 시작하며, 애플리케이션을 지원할 테이블이 필요합니다. 첫 번째 마이그레이션 파일은 001_creates_initial_setup.sql
이라는 이름으로 작성하며, 초기 항목들을 생성합니다.
$ cat << EOF > ./migrations/001_creates_initial_setup.sql
create schema myapp;
create table myapp.users(
id bigint constraint users_pk primary key not null,
first_name text constraint first_name_required check (first_name != '') not null);
EOF
아직 tern을 사용하기도 전에, 이미 많은 전제가 깔려 있습니다.
- 모든 마이그레이션 파일을 동일한 디렉터리에 두고 싶다
- 마이그레이션 파일을 평문 텍스트로 작성해 Git에 커밋하고 싶다
- 마이그레이션 파일을 특정 벤더 전용 언어가 아닌 SQL로 작성하고 싶다
첫 번째 마이그레이션 실행
이제 tern을 실행해 봅시다.
$ tern migrate \
--migrations ./migrations \
--version-table myapp_schema_version
정상적으로 실행되었습니다!
이제 데이터베이스에 스키마와 테이블이 생성되었는지 확인합니다.
$ psql -c '\dn myapp'
List of schemas
┌───────┬───────┐
│ Name │ Owner │
├───────┼───────┤
│ myapp │ app │
└───────┴───────┘
$ psql -c '\d myapp.users'
Table "myapp.users"
┌────────────┬────────┬───────────┬──────────┬─────────┐
│ Column │ Type │ Collation │ Nullable │ Default │
├────────────┼────────┼───────────┼──────────┼─────────┤
│ id │ bigint │ │ not null │ │
│ first_name │ text │ │ not null │ │
└────────────┴────────┴───────────┴──────────┴─────────┘
여기서 --version-table
옵션을 통해 tern이 버전을 추적할 테이블 이름을 지정했습니다. 따라서 public
스키마에 myapp_schema_version
테이블이 생성되어 있고, 현재 버전이 1임을 확인할 수 있습니다.
$ psql -c '\d public.myapp_schema_version'
Table "public.myapp_schema_version"
┌─────────┬─────────┬───────────┬──────────┬─────────┐
│ Column │ Type │ Collation │ Nullable │ Default │
├─────────┼─────────┼───────────┼──────────┼─────────┤
│ version │ integer │ │ not null │ │
└─────────┴─────────┴───────────┴──────────┴─────────┘
$ psql -c 'select version from public.myapp_schema_version'
┌─────────┐
│ version │
├─────────┤
│ 1 │
└─────────┘
이 패턴을 통해 tern은 프로젝트마다 자체 스키마 네임스페이스를 두고, 버전 관리 테이블도 프로젝트 단위로 유지할 수 있게 해줍니다.
요약하면, 우리는 다음과 같은 원칙을 따릅니다.
- 프로젝트는 데이터베이스 안에서 자체 네임스페이스(스키마)를 가진다.
- 마이그레이션 도구는 동일한 데이터베이스 내에서 프로젝트별로 스키마 버전을 관리한다.
스키마 진화
시간이 지나면서 스키마는 확장되어야 합니다. 이번에는 users
테이블에 last_name
컬럼을 추가해 봅시다.
$ cat << EOF > ./migrations/002_adds_last_name_to_users.sql
alter table myapp.users add column last_name text constraint last_name_required check (last_name != '') not null;
EOF
마이그레이션 디렉터리는 이제 이렇게 구성됩니다.
$ ls -1 migrations/
001_creates_initial_setup.sql
002_adds_last_name_to_users.sql
이제 tern을 다시 실행하고 결과를 확인합니다.
$ tern migrate \
--migrations ./migrations \
--version-table myapp_schema_version
$ psql -c '\d myapp.users'
Table "myapp.users"
┌────────────┬────────┬───────────┬──────────┬─────────┐
│ Column │ Type │ Collation │ Nullable │ Default │
├────────────┼────────┼───────────┼──────────┼─────────┤
│ id │ bigint │ │ not null │ │
│ first_name │ text │ │ not null │ │
│ last_name │ text │ │ not null │ │
└────────────┴────────┴───────────┴──────────┴─────────┘
$ psql -c 'select version from public.myapp_schema_version'
┌─────────┐
│ version │
├─────────┤
│ 2 │
└─────────┘
이제 프로젝트를 클론한 어떤 개발자라도 tern 마이그레이션을 실행하면 같은 데이터베이스 스키마 상태를 얻게 됩니다.
주의사항: 황금률
가령, last_name
컬럼이 불필요하다고 판단했다면 어떻게 할까요? 단순히 002_adds_last_name_to_users.sql
파일을 삭제해도 되지 않을까 생각할 수 있습니다. 하지만 이는 잘못된 접근입니다.
프로덕션을 포함한 이미 존재하는 데이터베이스들은 last_name
컬럼을 가지고 있을 것이고, 마이그레이션 파일 삭제 사실을 알 수 없기 때문입니다.
데이터베이스 마이그레이션의 황금률은 다음과 같습니다:
- 절대 기존 마이그레이션 파일을 수정하거나 삭제하지 말 것
- 항상 새로운 마이그레이션 파일을 추가하여 변경할 것
따라서 last_name
컬럼을 제거하려면 새로운 파일 003_drops_last_name_from_users.sql
을 만들어야 합니다.
alter table myapp.users drop column last_name;
추가 사항 및 단순화된 예시
위의 예시는 개념을 단순화해 설명한 것입니다. 몇 가지 추가적인 점을 짚어보겠습니다.
환경 변수 (Env Vars)
예제에서 tern과 psql이 별도의 연결 정보를 주지 않아도 동작한 이유는 환경 변수 때문입니다.
- PGHOST
- PGPORT
- PGDATABASE
- PGUSER
- PGPASSWORD
psql
은 libpq를 사용하기 때문에 이 환경 변수를 자동으로 인식합니다. tern 역시 libpq에 의존하지 않지만 동일한 변수들을 인식합니다.
즉, tern은 PostgreSQL의 생태계 및 관례와 잘 어울리도록 설계되었습니다.
설치 방법
tern은 매우 간단히 설치할 수 있습니다.
- 바이너리를 다운로드하고 압축 해제
tern
실행 파일을 PATH에 추가- 실행 확인
$ tern version
tern v2.3.2
추가 의존성도 없습니다. JVM, Python, Node.js 같은 환경을 설치할 필요도 없습니다.
롤백 기능
tern은 ---- create above / drop below ----
주석을 활용하면 이전 버전으로 롤백하거나 특정 버전까지만 적용할 수도 있습니다.
alter table myapp.users add column last_name text constraint last_name_required check (last_name != '') not null;
---- create above / drop below ----
alter table myapp.users drop column last_name;
하지만 주의할 점은, 데이터가 추가되었다가 컬럼이 삭제되면 데이터는 영구적으로 손실됩니다. 따라서 실무에서는 되도록 앞으로만 진행하는 단방향 마이그레이션 방식이 권장됩니다.
결론
많은 마이그레이션 도구들은 무겁고 복잡해 설치와 학습에 부담을 줍니다. 하지만 tern은 매우 가볍고 단순하여 프로젝트 시작 단계부터 도입하기에 적합합니다.
tern이 충족하는 조건을 다시 정리해봅시다:
- 마이그레이션 파일을 동일한 디렉터리에 보관할 수 있다
- 평문 텍스트 형식으로 Git에 버전 관리할 수 있다
- 표준 SQL을 사용하며 벤더 종속적이지 않다
- 프로젝트별 네임스페이스를 데이터베이스에 가질 수 있다
- 프로젝트 단위로 버전을 관리하는 테이블을 같은 데이터베이스에 유지한다
- PostgreSQL의 관례와 생태계에 잘 어울린다
- 설치와 실행이 간단해 개발자와 CI 환경에서 쉽게 사용할 수 있다
이러한 요구를 모두 충족하는 도구는 드뭅니다. tern은 그 조건을 모두 만족시키는 훌륭한 선택이므로, 아직 사용하지 않았다면 다음 프로젝트에서 꼭 시도해 보시길 권장합니다.