tern으로 PostgreSQL 데이터베이스 스키마 마이그레이션 관리하기
저자: Manni Wood
2025년 8월 29일
tern은 시간이 지나면서 발생하는 PostgreSQL 데이터베이스 스키마 변경을 관리하는 가벼운 마이그레이션 도구입니다. 이 글에서는 마이그레이션 파일을 만들고 실행하는 방법부터 스키마 버전 관리, 롤백, 운영 환경에서 지켜야 할 원칙까지 예제로 살펴봅니다.
여기서 ‘데이터베이스 마이그레이션’은 오라클에서 PostgreSQL로 이전하는 것처럼 서로 다른 데이터베이스 간에 데이터를 옮기는 작업이 아니라, 기존 데이터베이스 스키마를 변경하는 작업을 뜻합니다.
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로 작성한다.
첫 번째 PostgreSQL 스키마 마이그레이션 실행
이제 다음 명령으로 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 │
└─────────┘
이 패턴을 사용하면 프로젝트마다 자체 스키마 네임스페이스를 두고 버전 관리 테이블도 프로젝트 단위로 유지할 수 있습니다.
- 프로젝트는 데이터베이스 안에서 자체 네임스페이스(스키마)를 가진다.
- 마이그레이션 도구는 동일한 데이터베이스 내에서 프로젝트별로 스키마 버전을 관리한다.
새 마이그레이션으로 스키마 변경하기
시간이 지나면서 스키마는 확장되어야 합니다. 이번에는 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;
tern 사용 시 알아둘 사항
위 예시는 핵심 개념을 설명하기 위해 단순화한 것입니다. 실제 사용 전에 환경 변수, 설치 방법, 롤백 방식도 알아두어야 합니다.
PostgreSQL 연결 환경 변수
예제에서 tern과 psql이 별도의 연결 정보 없이 동작한 이유는 다음 환경 변수를 사용하기 때문입니다.
PGHOSTPGPORTPGDATABASEPGUSERPGPASSWORD
psql은 libpq를 사용하므로 이 환경 변수들을 자동으로 인식합니다. tern은 libpq에 의존하지 않지만 동일한 변수들을 인식합니다. 즉, tern은 PostgreSQL 생태계의 관례에 맞게 설계되었습니다.
tern 설치 방법
tern은 다음 순서로 설치할 수 있습니다.
- 바이너리를 다운로드하고 압축을 해제합니다.
tern실행 파일을 PATH에 추가합니다.- 다음 명령으로 실행 여부를 확인합니다.
$ tern version
tern v2.3.2
추가 의존성은 없습니다. JVM, Python, Node.js 같은 환경을 설치할 필요도 없습니다.
tern 롤백 기능과 데이터 손실 주의
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을 활용한 PostgreSQL 스키마 관리 요약
많은 마이그레이션 도구는 설치와 학습에 부담을 줄 수 있지만, tern은 가볍고 단순해 프로젝트 시작 단계부터 도입하기에 적합합니다.
tern이 충족하는 조건을 정리하면 다음과 같습니다.
- 마이그레이션 파일을 동일한 디렉터리에 보관할 수 있다.
- 평문 텍스트 형식으로 Git에서 버전 관리할 수 있다.
- 표준 SQL을 사용하며 벤더 종속적이지 않다.
- 프로젝트별 네임스페이스를 데이터베이스에 구성할 수 있다.
- 프로젝트 단위 버전 관리 테이블을 같은 데이터베이스에 유지한다.
- PostgreSQL의 관례와 생태계에 잘 어울린다.
- 설치와 실행이 간단해 개발자와 CI 환경에서 쉽게 사용할 수 있다.
이러한 요구를 모두 충족하는 도구는 드뭅니다. 아직 tern을 사용하지 않았다면 다음 프로젝트에서 시도해 보시길 권장합니다.
tern과 PostgreSQL 스키마 마이그레이션 FAQ
tern은 어떤 데이터베이스 마이그레이션 도구인가요?
tern은 SQL 마이그레이션 파일을 순서대로 적용하고 버전 테이블로 현재 스키마 상태를 추적하는 가벼운 도구입니다. 이 글에서 마이그레이션은 서로 다른 데이터베이스 간 이전이 아니라 기존 데이터베이스 스키마를 변경하는 작업을 뜻합니다.
tern은 PostgreSQL 스키마 버전을 어떻게 관리하나요?
tern migrate 명령의 --version-table 옵션으로 버전 관리 테이블 이름을 지정할 수 있습니다. 마이그레이션이 적용될 때마다 이 테이블의 버전 값이 갱신됩니다.
이미 적용한 마이그레이션 파일을 수정하거나 삭제해도 되나요?
수정하거나 삭제하면 안 됩니다. 이미 해당 파일을 적용한 데이터베이스와 새로 구성한 데이터베이스의 스키마 상태가 달라질 수 있으므로, 변경이 필요하면 새로운 마이그레이션 파일을 추가해야 합니다.
tern과 psql이 인식하는 PostgreSQL 환경 변수는 무엇인가요?
이 예제에서는 PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD를 사용합니다. psql은 libpq를 통해 해당 변수를 인식하며, tern도 같은 변수들을 인식합니다.
tern에서 마이그레이션을 롤백할 수 있나요?
마이그레이션 파일에 ---- create above / drop below ---- 주석과 되돌리기 SQL을 작성하면 이전 버전으로 롤백하거나 특정 버전까지만 적용할 수 있습니다. 다만 컬럼 삭제처럼 데이터 손실이 발생할 수 있는 작업은 주의해야 하며, 실무에서는 단방향 마이그레이션이 권장됩니다.