Wednesday, August 9, 2023

Flyway를 통한 데이터베이스 스키마의 체계적 진화

소프트웨어 개발의 복잡성이 증가함에 따라, 애플리케이션 코드만큼이나 데이터베이스 스키마를 체계적으로 관리하는 것의 중요성이 부각되고 있습니다. 개발 초기 단계에서는 수동으로 SQL 스크립트를 실행하는 방식이 가능할지 모르지만, 팀 단위 협업이 이루어지고 개발, 테스트, 운영 등 여러 환경을 관리해야 하는 상황에서는 곧 한계에 부딪히게 됩니다. 개발자마다 로컬 데이터베이스의 상태가 달라지고, 특정 버전의 애플리케이션이 호환되지 않는 스키마와 충돌하며, 배포 과정에서 어떤 스크립트를 실행해야 할지 몰라 혼란이 발생하는 것은 드문 일이 아닙니다. 이러한 문제들은 '데이터베이스 스키마 버전 관리의 부재'라는 공통된 원인에서 비롯됩니다.

이러한 혼돈을 해결하기 위한 접근 방식이 바로 '진화적 데이터베이스 설계(Evolutionary Database Design)'이며, 이를 실현하는 도구가 데이터베이스 마이그레이션 툴입니다. Flyway는 이 분야에서 가장 널리 사용되는 오픈 소스 도구 중 하나로, 단순함과 명확한 컨벤션을 통해 데이터베이스 스키마 변경을 코드처럼 버전 관리하고, 예측 가능하며, 반복 가능한 방식으로 자동화할 수 있도록 지원합니다. 이 글에서는 Flyway의 핵심 원리를 깊이 있게 탐구하고, 실제 프로젝트에 적용하는 방법과 팀 협업 시 발생할 수 있는 문제들을 해결하기 위한 고급 전략 및 모범 사례들을 체계적으로 살펴보겠습니다.

1. 데이터베이스 마이그레이션의 본질과 필요성

데이터베이스 마이그레이션은 단순히 스키마를 변경하는 행위를 넘어, 데이터베이스의 상태를 특정 버전에서 다른 버전으로 안정적으로 전환하는 모든 과정을 포함합니다. 이는 애플리케이션의 기능 추가, 버그 수정, 성능 개선 등과 맞물려 필연적으로 발생하는 작업이며, 그 중요성은 다음과 같은 구체적인 상황에서 명확하게 드러납니다.

1.1 스키마의 지속적인 개선과 리팩토링

소프트웨어는 살아있는 유기체와 같아서 비즈니스 요구사항의 변화에 따라 끊임없이 진화합니다. 초기에 완벽하게 설계된 스키마라 할지라도, 새로운 기능이 추가되면서 테이블, 컬럼, 인덱스, 제약 조건 등의 변경이 필요하게 됩니다. 예를 들어, 초기 사용자 모델에 없었던 '프로필 이미지 URL' 필드를 추가하거나, 검색 성능 향상을 위해 '사용자 이름' 컬럼에 인덱스를 생성하는 작업 등이 여기에 해당합니다. 마이그레이션 도구가 없다면, 이러한 변경 사항을 각 개발자가 수동으로 자신의 로컬 DB에 적용하고, 운영 환경 배포 시 DBA가 별도의 스크립트를 실행해야 하는 번거로움과 위험이 따릅니다.

1.2 다중 환경 간의 스키마 동기화

현대적인 개발 프로세스는 로컬 개발 환경, 통합 테스트를 위한 CI(Continuous Integration) 환경, QA 테스트 환경, 스테이징(Staging) 환경, 그리고 최종적으로 운영(Production) 환경 등 여러 단계로 구성됩니다. 각 환경의 데이터베이스 스키마가 동일한 버전을 유지하는 것은 애플리케이션의 안정적인 동작을 위한 필수 전제 조건입니다. 만약 개발 환경에만 적용된 스키마 변경 사항을 QA 환경에 누락한다면, 해당 기능을 테스트하는 과정에서 원인을 알 수 없는 오류가 발생하여 불필요한 시간 낭비를 초래할 것입니다. 데이터베이스 마이그레이션은 모든 환경에 동일한 버전의 스키마 변경이 순차적으로 적용됨을 보장하여 이러한 문제를 원천적으로 차단합니다.

1.3 팀 협업의 효율성 증대

여러 개발자가 하나의 프로젝트에 참여할 때, 각자의 로컬 데이터베이스 상태가 달라 발생하는 '내 PC에선 됐는데...' 문제는 협업의 효율을 크게 저하시킵니다. A 개발자가 추가한 테이블이 B 개발자의 환경에는 없어서 애플리케이션이 실행조차 되지 않는 상황이 대표적입니다. 모든 스키마 변경 사항을 버전 관리 시스템(예: Git)으로 관리되는 마이그레이션 스크립트를 통해 공유함으로써, 팀원들은 간단한 명령어 하나로 자신의 로컬 데이터베이스를 최신 상태로 손쉽게 업데이트할 수 있습니다. 이는 새로운 팀원이 프로젝트에 합류했을 때 개발 환경을 설정하는 과정을 극적으로 단순화하는 효과도 가져옵니다.

1.4 안정적인 배포 및 롤백 기반 마련

자동화된 배포 파이프라인에서 데이터베이스 마이그레이션은 핵심적인 단계입니다. 새로운 버전의 애플리케이션을 배포하기 직전, 마이그레이션 도구를 실행하여 데이터베이스 스키마를 애플리케이션 코드와 호환되는 상태로 먼저 업데이트합니다. 이 과정이 자동화되면 배포 과정에서 발생할 수 있는 인간의 실수를 줄이고, 배포 시간을 단축할 수 있습니다. 또한, 잘 작성된 마이그레이션 스크립트는 데이터베이스의 변경 이력을 명확하게 남기므로, 심각한 문제가 발생했을 때 어떤 변경이 이루어졌는지 추적하고 대응 전략을 수립하는 데 중요한 단서를 제공합니다.

2. Flyway 작동 원리: 세 가지 핵심 요소

Flyway의 강력함은 그 단순함에 있습니다. 복잡한 설정 파일이나 XML 없이, 몇 가지 핵심적인 개념과 명명 규칙(Convention)을 기반으로 동작합니다. Flyway의 작동 방식을 이해하기 위해서는 마이그레이션 스크립트, 스키마 히스토리 테이블, 그리고 Flyway 클라이언트라는 세 가지 구성 요소를 알아야 합니다.

2.1 마이그레이션 스크립트 (Migrations): 변경의 기록

마이그레이션 스크립트는 데이터베이스에 적용할 스키마 변경 사항을 담고 있는 파일입니다. Flyway는 파일의 이름 규칙을 통해 스크립트의 종류와 순서를 파악합니다. 가장 일반적으로 사용되는 스크립트 유형은 다음과 같습니다.

  • 버전 기반 마이그레이션 (Versioned Migrations): 가장 핵심적인 마이그레이션 유형입니다. 한 번 적용되면 변경이 불가능한(immutable) 스크립트로, 주로 테이블 생성, 컬럼 추가/수정/삭제 등 DDL(Data Definition Language) 구문을 담습니다. 파일 이름은 V<버전>__<설명>.sql 형식을 따릅니다.
    • V: 버전 기반 마이그레이션을 의미하는 접두사입니다.
    • <버전>: 마이그레이션의 순서를 결정하는 버전 번호입니다. 1, 1.1, 202310271030 등 점(.)과 숫자, 언더스코어(_)로 구성될 수 있습니다. 버전은 오름차순으로 정렬되어 순서대로 실행됩니다.
    • __: 버전과 설명을 구분하는 이중 언더스코어입니다.
    • <설명>: 이 마이그레이션이 어떤 작업을 수행하는지 설명하는 텍스트입니다. (예: Create_user_table, Add_email_to_users)
    • 예시: V1__Create_user_table.sql, V2.1__Add_indexes_on_user_name.sql
  • 반복 가능 마이그레이션 (Repeatable Migrations): 뷰(View), 저장 프로시저(Stored Procedure), 함수(Function)와 같이 스키마 구조 변경이 아니라 정의 자체가 변경될 수 있는 객체를 관리하는 데 사용됩니다. 이 스크립트는 내용(체크섬)이 변경될 때마다 다시 적용됩니다. 파일 이름은 R__<설명>.sql 형식을 따릅니다.
    • R: 반복 가능 마이그레이션을 의미하는 접두사입니다.
    • 예시: R__Create_or_update_user_view.sql
  • 자바 기반 마이그레이션 (Java-based Migrations): 단순 SQL로 처리하기 어려운 복잡한 데이터 변환이나 절차적인 로직이 필요한 경우 사용합니다. 예를 들어, 특정 컬럼의 데이터를 암호화하거나, 여러 테이블의 데이터를 조합하여 새로운 데이터를 생성하는 등의 작업에 유용합니다. 클래스 이름이 파일 이름과 동일한 명명 규칙을 따릅니다.

2.2 스키마 히스토리 테이블 (Schema History Table): 진화의 증인

Flyway가 어떤 마이그레이션이 이미 데이터베이스에 적용되었는지를 추적하기 위해 사용하는 핵심적인 메타데이터 테이블입니다. 기본적으로 flyway_schema_history라는 이름으로 생성되며, Flyway가 처음 migrate 명령을 실행할 때 대상 데이터베이스에 이 테이블이 없으면 자동으로 생성합니다.

이 테이블은 다음과 같은 중요한 정보를 저장합니다.

컬럼명 설명
installed_rank 마이그레이션이 적용된 순서 (PK)
version 마이그레이션 스크립트의 버전. 반복 가능 마이그레이션의 경우 null.
description 스크립트 파일 이름에서 가져온 설명.
type 마이그레이션 유형 (SQL, JDBC 등).
script 실행된 스크립트의 파일 이름.
checksum 스크립트 파일 내용의 체크섬. 이미 적용된 마이그레이션이 실수로 변경되었는지 감지하는 데 사용됩니다.
installed_by 마이그레이션을 실행한 데이터베이스 사용자.
installed_on 마이그레이션이 적용된 시간 (타임스탬프).
execution_time 스크립트 실행에 소요된 시간 (밀리초).
success 마이그레이션 성공 여부 (boolean).

flyway migrate 명령이 실행되면, Flyway는 파일 시스템(또는 클래스패스)에 있는 마이그레이션 스크립트 목록과 flyway_schema_history 테이블의 내용을 비교합니다. 테이블에 기록되지 않은 새로운 버전의 스크립트가 발견되면, 버전 순서대로 해당 스크립트를 데이터베이스에 실행하고 그 결과를 히스토리 테이블에 기록합니다. 이 과정을 통해 항상 최신 상태를 유지할 수 있습니다.

2.3 Flyway 클라이언트 (Client): 실행의 주체

Flyway 클라이언트는 실제로 마이그레이션을 수행하는 실행 도구입니다. 다양한 형태로 제공되어 프로젝트 환경에 맞게 선택할 수 있습니다.

  • 커맨드 라인 도구 (Command-line Tool): 가장 기본적인 형태로, 터미널에서 직접 Flyway 명령을 실행할 수 있습니다. 플랫폼별로 독립적인 실행 파일을 제공하여 어떤 프로젝트에서든 쉽게 사용할 수 있습니다.
  • 빌드 도구 플러그인 (Maven/Gradle Plugin): Maven이나 Gradle과 같은 자바 빌드 도구에 통합하여 사용합니다. mvn flyway:migrategradle flywayMigrate와 같은 명령어로 빌드 프로세스의 일부로 마이그레이션을 실행할 수 있어 CI/CD 파이프라인에 통합하기 용이합니다.
  • Java API: Spring Boot와 같은 애플리케이션 프레임워크에 내장되거나, 코드 내에서 직접 Flyway의 동작을 제어하고 싶을 때 사용합니다. 애플리케이션 시작 시 자동으로 마이그레이션을 수행하도록 구성하는 것이 일반적인 사용 사례입니다.

이 클라이언트들은 다양한 명령을 제공하며, 가장 핵심적인 명령은 다음과 같습니다.

  • migrate: 현재 스키마 버전을 확인하고, 적용되지 않은 모든 마이그레이션을 순서대로 실행합니다. 가장 많이 사용되는 명령입니다.
  • info: 모든 마이그레이션의 상태를 테이블 형태로 보여줍니다. 어떤 스크립트가 적용되었고, 어떤 것이 대기 중(Pending)인지 한눈에 파악할 수 있습니다.
  • validate: flyway_schema_history에 기록된 체크섬과 파일 시스템에 있는 스크립트 파일의 체크섬을 비교합니다. 만약 이미 적용된 마이그레이션 파일이 수정되었다면, 유효성 검사에 실패하여 잠재적인 문제를 미리 알려줍니다.
  • clean: 대상 데이터베이스의 모든 객체(테이블, 뷰 등)를 삭제합니다. 주로 개발 및 테스트 환경에서 데이터베이스를 초기 상태로 되돌리고 싶을 때 사용하며, 운영 환경에서는 절대 사용해서는 안 됩니다.
  • baseline: 기존에 데이터가 있는 데이터베이스에 Flyway를 처음 도입할 때 사용합니다. 지정된 버전을 기준으로 flyway_schema_history 테이블을 생성하고, 해당 버전까지의 마이그레이션은 이미 적용된 것으로 간주합니다.
  • repair: validate 실패 시, flyway_schema_history 테이블의 체크섬을 현재 파일의 체크섬으로 업데이트하여 불일치 문제를 해결합니다. 또는 실패한 마이그레이션 기록을 제거하는 데 사용될 수도 있습니다.

3. 실전 Flyway: 설치부터 마이그레이션 실행까지

이제 이론을 바탕으로 실제 Flyway를 사용하여 데이터베이스 마이그레이션을 수행하는 과정을 단계별로 살펴보겠습니다. 여기서는 가장 범용적인 커맨드 라인 도구(CLI)를 사용하는 시나리오를 기준으로 설명합니다.

3.1 Flyway CLI 설치 및 프로젝트 구조 설정

먼저 Flyway 공식 웹사이트에서 자신의 운영체제에 맞는 커맨드 라인 도구를 다운로드하여 설치하고, PATH 환경 변수에 등록하여 터미널 어디에서든 flyway 명령을 실행할 수 있도록 준비합니다.

다음으로, 프로젝트 폴더를 생성하고 다음과 같은 구조를 만듭니다.

my-flyway-project/
├── flyway.conf
└── sql/
    ├── V1__Create_user_table.sql
    └── V2__Add_email_to_user_table.sql
  • my-flyway-project/: 프로젝트의 루트 디렉토리입니다.
  • flyway.conf: Flyway의 설정을 담는 파일입니다.
  • sql/: 마이그레이션 SQL 스크립트들을 위치시킬 디렉토리입니다.

3.2 설정 파일 작성 (flyway.conf)

flyway.conf 파일은 Flyway가 데이터베이스에 연결하고 마이그레이션 스크립트를 찾는 데 필요한 정보를 제공합니다. 아래는 MySQL 데이터베이스에 연결하는 예시입니다.

# flyway.conf

# ---------------------------------
# JDBC Connection properties
# ---------------------------------
# 데이터베이스 연결 URL
flyway.url=jdbc:mysql://localhost:3306/my_app_db?createDatabaseIfNotExist=true
# 데이터베이스 사용자 이름
flyway.user=my_user
# 데이터베이스 비밀번호
flyway.password=my_secret_password

# ---------------------------------
# Migration script locations
# ---------------------------------
# 마이그레이션 스크립트가 위치한 경로
# 여러 경로를 콤마(,)로 구분하여 지정할 수 있습니다.
# filesystem: 접두사는 파일 시스템 경로를 의미합니다.
flyway.locations=filesystem:./sql

# ---------------------------------
# Schema history table
# ---------------------------------
# 스키마 히스토리 테이블의 이름을 지정할 수 있습니다. (기본값: flyway_schema_history)
# flyway.table=schema_version

이 설정 파일 하나로 Flyway는 대상 데이터베이스와 마이그레이션할 스크립트의 위치를 모두 알 수 있게 됩니다.

3.3 마이그레이션 스크립트 작성

이제 sql 디렉토리 안에 실제 스키마를 변경할 SQL 스크립트를 작성합니다.

V1__Create_user_table.sql


CREATE TABLE users (
    id BIGINT NOT NULL AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

V2__Add_email_to_user_table.sql


ALTER TABLE users
ADD COLUMN email VARCHAR(100) NOT NULL UNIQUE AFTER username;

이 두 스크립트는 Git과 같은 버전 관리 시스템에 의해 프로젝트 코드와 함께 관리되어야 합니다.

3.4 마이그레이션 실행 및 상태 확인

모든 준비가 끝났습니다. 터미널에서 프로젝트 루트 디렉토리(my-flyway-project/)로 이동하여 다음 명령들을 차례로 실행해 봅니다.

1. 상태 확인 (info)

먼저 flyway info 명령으로 현재 상태를 확인합니다. 아직 아무 작업도 하지 않았으므로 모든 스크립트가 'Pending' 상태로 표시됩니다.

$ flyway info
Flyway Community Edition 9.x.x by Redgate
Database: jdbc:mysql://localhost:3306/my_app_db (MySQL 8.0)
Schema version: << Empty Schema >>

+-----------+---------+----------------------------+----------+---------------------+---------+
| Category  | Version | Description                | Type     | Installed On        | State   |
+-----------+---------+----------------------------+----------+---------------------+---------+
| Versioned | 1       | Create user table          | SQL      |                     | Pending |
| Versioned | 2       | Add email to user table    | SQL      |                     | Pending |
+-----------+---------+----------------------------+----------+---------------------+---------+

2. 마이그레이션 실행 (migrate)

이제 flyway migrate 명령으로 실제 마이그레이션을 실행합니다.

$ flyway migrate
Flyway Community Edition 9.x.x by Redgate
Database: jdbc:mysql://localhost:3306/my_app_db (MySQL 8.0)
Successfully validated 2 migrations (execution time 00:00.010s)
Creating Schema History table `my_app_db`.`flyway_schema_history`...
Current version of schema `my_app_db`: << Empty Schema >>
Migrating schema `my_app_db` to version 1 - Create user table
Migrating schema `my_app_db` to version 2 - Add email to user table
Successfully applied 2 migrations to schema `my_app_db` (execution time 00:00.512s)

출력 메시지를 보면, Flyway가 flyway_schema_history 테이블을 먼저 생성하고, 버전 1과 2를 순서대로 성공적으로 적용했음을 알 수 있습니다. 이제 실제 데이터베이스에 접속해 보면 users 테이블과 flyway_schema_history 테이블이 생성된 것을 확인할 수 있습니다.

3. 다시 상태 확인 (info)

다시 flyway info 명령을 실행하면 모든 스크립트가 'Success' 상태로 변경된 것을 볼 수 있습니다.

$ flyway info
Flyway Community Edition 9.x.x by Redgate
Database: jdbc:mysql://localhost:3306/my_app_db (MySQL 8.0)
Schema version: 2

+-----------+---------+----------------------------+----------+---------------------+---------+
| Category  | Version | Description                | Type     | Installed On        | State   |
+-----------+---------+----------------------------+----------+---------------------+---------+
| Versioned | 1       | Create user table          | SQL      | 2023-10-27 11:00:00 | Success |
| Versioned | 2       | Add email to user table    | SQL      | 2023-10-27 11:00:01 | Success |
+-----------+---------+----------------------------+----------+---------------------+---------+

이 상태에서 flyway migrate를 다시 실행하면, Flyway는 적용할 새로운 마이그레이션이 없다고 판단하고 아무 작업도 수행하지 않습니다. 이것이 Flyway가 멱등성(idempotent)을 보장하는 방식입니다.

4. 애플리케이션 통합 전략: Spring Boot를 중심으로

Flyway는 독립적인 CLI 도구로도 강력하지만, 실제로는 애플리케이션의 생명주기와 함께 관리될 때 더 큰 시너지를 발휘합니다. 특히 Spring Boot와의 통합은 매우 간단하고 효율적입니다.

4.1 Spring Boot 자동 설정의 마법

Spring Boot는 클래스패스에 특정 라이브러리가 존재하면 관련 설정을 자동으로 구성해주는 기능을 갖추고 있습니다. Flyway도 그 대상 중 하나입니다.

1. 의존성 추가

Maven(pom.xml) 또는 Gradle(build.gradle.kts)에 Flyway 의존성을 추가하기만 하면 됩니다. 데이터베이스 드라이버(예: mysql-connector-j)도 함께 필요합니다.

Maven (pom.xml)


<dependencies>
    <dependency>
        <groupId>org.flywaydb</groupId>
        <artifactId>flyway-core</artifactId>
    </dependency>
    <!-- For MySQL -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2. 설정 파일 구성 (application.yml)

src/main/resources/application.yml 파일에 데이터베이스 연결 정보만 추가하면 됩니다. Flyway 관련 설정은 spring.flyway 접두사를 사용하여 지정할 수 있습니다.


spring:
  # 데이터소스 설정
  datasource:
    url: jdbc:mysql://localhost:3306/my_app_db
    username: my_user
    password: my_secret_password
    driver-class-name: com.mysql.cj.jdbc.Driver

  # Flyway 설정
  flyway:
    # Flyway 활성화 여부 (기본값: true)
    enabled: true
    # 마이그레이션 스크립트 위치 (기본값: classpath:db/migration)
    locations:
      - classpath:db/migration/common
      - classpath:db/migration/mysql
    # baselineOnMigrate: 히스토리 테이블이 없을 때 자동으로 baseline을 수행할지 여부
    baseline-on-migrate: true

3. 스크립트 위치

Spring Boot의 기본 컨벤션에 따라, src/main/resources/db/migration 디렉토리에 SQL 스크립트 파일들을 위치시킵니다.

이것으로 설정은 끝입니다. 이제 Spring Boot 애플리케이션을 실행하면, 애플리케이션 컨텍스트가 로드되는 과정에서 Flyway가 자동으로 주입된 DataSource를 사용하여 migrate 작업을 수행합니다. 즉, 애플리케이션이 시작될 때마다 데이터베이스 스키마는 항상 최신 상태로 유지됩니다. 개발자는 더 이상 데이터베이스 상태를 신경 쓸 필요 없이 코드 개발에만 집중할 수 있습니다.

4.2 커스텀 로직: FlywayMigrationStrategy

때로는 기본 마이그레이션 동작 외에 추가적인 로직을 수행하고 싶을 수 있습니다. 예를 들어 마이그레이션 전에 clean을 먼저 수행하여 항상 깨끗한 상태에서 시작하고 싶거나(개발 환경에서 유용), 마이그레이션 실패 시 특정 복구 로직을 실행하고 싶을 수 있습니다. 이때 FlywayMigrationStrategy 인터페이스를 구현하여 커스텀 빈(Bean)을 등록하면 됩니다.


@Configuration
public class FlywayConfiguration {

    @Bean
    public FlywayMigrationStrategy cleanMigrateStrategy() {
        return flyway -> {
            // 마이그레이션 실행 전, 데이터베이스를 깨끗하게 정리
            flyway.clean();
            // 이후 마이그레이션 실행
            flyway.migrate();
        };
    }
}

이러한 전략은 특정 Spring 프로파일(예: local, test)에서만 활성화되도록 하여 환경별로 다른 동작을 제어할 수 있습니다.

5. 팀 협업과 운영을 위한 고급 활용법 및 모범 사례

Flyway를 성공적으로 도입하는 것은 단순히 도구를 사용하는 것을 넘어, 팀의 워크플로우와 문화에 녹여내는 과정입니다. 여러 개발자가 협업하고, 안정적인 운영 환경을 유지하기 위해 다음과 같은 고급 전략과 모범 사례를 고려해야 합니다.

5.1 버전 충돌 방지: 버전 관리 전략

여러 개발자가 각자의 기능 브랜치에서 동시에 데이터베이스 변경 작업을 할 때, 마이그레이션 버전 번호가 충돌하는 문제가 발생할 수 있습니다. 예를 들어, 두 개발자가 모두 다음 버전을 V3으로 생각하고 V3__feature_A.sqlV3__feature_B.sql을 각각 생성하면, 브랜치를 병합할 때 충돌이 발생합니다.

  • 날짜/시간 기반 버전: 가장 널리 쓰이는 해결책은 버전 번호로 날짜와 시간을 사용하는 것입니다. 예를 들어, V20231027153000__Add_feature_A.sql와 같이 생성하면 거의 충돌할 일이 없습니다.
  • Jira 이슈 번호 등 활용: V3_JIRA-123__Feature_A.sql, V4_JIRA-124__Feature_B.sql 처럼 순차적인 번호는 유지하되, 관련 이슈 번호를 함께 기재하여 관리하는 방법도 있습니다. 이 경우, 개발 시작 전에 미리 버전을 선점하는 규칙이 필요할 수 있습니다.

5.2 마이그레이션 스크립트 테스트

애플리케이션 코드를 테스트하듯, 마이그레이션 스크립트도 테스트의 대상이 되어야 합니다. 잘못된 DDL 스크립트 하나가 운영 데이터베이스를 마비시킬 수 있기 때문입니다.

  • 문법 검사 (Syntax Check): H2와 같은 인메모리 데이터베이스를 사용하여 테스트를 실행하면, SQL 문법 오류를 빠르게 잡아낼 수 있습니다. 하지만 H2는 실제 운영 DB(MySQL, PostgreSQL 등)와 문법이 다르므로 이것만으로는 충분하지 않습니다.
  • Testcontainers를 활용한 통합 테스트: 가장 확실한 방법은 Testcontainers 라이브러리를 사용하는 것입니다. 테스트 실행 시 Docker를 이용해 실제 운영 환경과 동일한 데이터베이스(예: PostgreSQL 14) 컨테이너를 동적으로 생성합니다. 이 격리된 DB에 Flyway 마이그레이션을 적용하고, 이후 JPA/MyBatis 등의 로직이 정상 동작하는지 검증하는 통합 테스트를 작성할 수 있습니다. 이를 통해 DB 호환성 문제까지 완벽하게 검증할 수 있습니다.

5.3 무중단 배포를 위한 마이그레이션 전략

무중단 배포 환경에서는 구버전의 애플리케이션과 신버전의 애플리케이션이 일시적으로 공존하는 시점이 발생합니다. 이때 데이터베이스 스키마 변경은 매우 신중하게 이루어져야 합니다.

  • 파괴적인 변경(Destructive Change) 지양: 컬럼 삭제, 테이블 이름 변경, 컬럼 타입 변경 등은 하위 호환성을 깨뜨리는 파괴적인 변경입니다. 이러한 변경은 한 번의 배포로 이루어져서는 안 됩니다.
  • 확장-축소(Expand-Contract) 패턴:
    1. (확장) 1차 배포: 새로운 컬럼(email)을 추가하는 마이그레이션을 실행하고, 신규 버전의 코드는 이 컬럼에 값을 쓰기 시작하지만, 아직 읽지는 않습니다. 구버전 코드는 이 컬럼의 존재를 모른 채 정상 동작합니다.
    2. (적응) 2차 배포: 모든 애플리케이션 인스턴스가 신규 버전으로 교체된 후, 새로운 컬럼(email)을 읽기 시작하는 코드를 배포합니다.
    3. (축소) 3차 배포: 더 이상 사용되지 않는 예전 컬럼(예: old_email)을 삭제하는 마이그레이션을 실행합니다.
    이처럼 여러 단계에 걸쳐 점진적으로 스키마를 변경함으로써, 서비스 중단 없이 안전하게 데이터베이스를 진화시킬 수 있습니다.

5.4 스크립트 작성 원칙

  • 멱등성 유지: 스크립트는 여러 번 실행되어도 항상 동일한 결과를 내도록 작성하는 것이 안전합니다. 예를 들어, 테이블을 생성할 때는 CREATE TABLE IF NOT EXISTS ..., 인덱스를 추가할 때는 해당 인덱스가 이미 존재하는지 확인하는 로직을 추가하는 것이 좋습니다.
  • 트랜잭션 관리: Flyway는 기본적으로 각 마이그레이션 스크립트를 하나의 트랜잭션 안에서 실행합니다. 중간에 오류가 발생하면 전체가 롤백됩니다. (단, MySQL의 DDL처럼 암묵적 커밋을 유발하는 구문은 롤백되지 않으므로 주의해야 합니다.)
  • 가독성과 명확성: 스크립트 파일의 설명 부분(__<설명>.sql)은 어떤 변경인지 명확히 알 수 있도록 작성하고, 복잡한 SQL 구문에는 주석을 추가하여 동료 개발자들이 쉽게 이해할 수 있도록 배려해야 합니다.

Flyway는 단순한 도구를 넘어, 데이터베이스를 애플리케이션 코드와 동등한 수준으로 체계적으로 관리하고, 팀의 개발 문화를 한 단계 성숙시키는 강력한 촉매제입니다. 위에 제시된 원칙과 사례들을 프로젝트에 적용함으로써, 변화에 유연하게 대응하고 예측 가능한 배포를 실현하며, 궁극적으로는 더 안정적이고 견고한 소프트웨어를 만들어 나갈 수 있을 것입니다.


0 개의 댓글:

Post a Comment