ecsimsw

Flyway와 DB Migration / 우리 팀에서 Flyway를 사용하는 이유 본문

Flyway와 DB Migration / 우리 팀에서 Flyway를 사용하는 이유

JinHwan Kim 2021. 7. 17. 01:23

Flyway 소개하기

https://www.youtube.com/watch?v=pxDlj5jA9z4

 

 

DB Migration

DB Migration의 필요를 모를 수 있다. 솔직히 나는 몰랐다. 배포 후 데이터를 관리해본 경험이 없었고, 유지 보수 중 스키마 구조가 바뀌는 상황에 어떻게 대처하는지 생각해본 적 없었다.

 

사실 flyway를 검색하면 사용 방법이 아주 자세히 잘 나와있다. 그런데도 내가 이 글을 쓰는 이유는 배포 후 DB를 관리하고 유지 보수 해본 경험이 전혀 없는 학생들에게 '당신에게 곧 이런 문제 사항이 생길 것이고, flyway라는 툴은 이걸 이렇게 풀어준다.'를 소개하고 싶었다.

 

나중에 그런 경험을 만났을 때, 'DB Migration 또는 Flyway tool 라는 키워드가 있었던 거 같은데~' 정도의 생각이 들면, 나는 성공이다. 키워드 수준이라도 문제 해결의 방향을 알고 모르고의 차이는 크니까.

 

당신에게 곧 이런 문제 사항이 생길 것이고, 그걸 해결할 수 있는 flyway라는 툴이 있더라

상황을 하나 보여주려고 한다. SampleEntity를 예제로 사용할 것인데, 배포가 이미 완료된 더 복잡하고 이미 쌓인 정보도 많은 데이터라고 상상해주길 바란다.

@Entity
public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

이런 필드로 SampleEntity가 배포되고 데이터가 쌓였던 상황인데, 신기술을 개발하는 도중에 int age라는 필드를 추가하게 되었다.

 

@Entity
public class SampleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Integer age;
}

 

자, 이런 상황에서 당신은 어떻게 이 SampleEntity 변화를 처리할 것인가. 잠시 고민해봤으면 좋겠다.

 

나는 배포 서버 DB에 직접 들어가서 테이블을 직접 수정해왔다. 그건 쉬운 작업이 아니다. 솔직히 엄청 귀찮다. 배포 후 기능을 추가하면서 스키마가 변경되는 일이 많았고, 매번 일일히 작업하는 게 번거로울 뿐 아니라 실수하기도 딱 좋다.

 

ALTER TABLE sample_entity ADD COLUMN age integer default 0

 

로컬에서 DB 변경 사항을 추가하는 것으로 배포 이후에는 알아서 관리될 수 있다면 얼마나 좋을까. 그냥 코드를 관리하는 것처럼 DB 변경 사항을 관리할 순 없을까?

 

Flyway라는 툴을 사용하면 그걸 해소할 수 있다. Flyway를 사용하고 해결하는 과정을 천천히 보여주려고 한다.

 

직접 적용 과정을 보여드리겠습니다.

예외 확인하기 : Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'age' in 'field list'

 

우선 sampleEntity 데이터를 미리 3개 넣어두었다. 이 상황에서 int age 필드가 추가하고 DB 스키마를 수정하지 않으면 SQL 에러가 뜨는 것을 확인한다. 

 

 

자 이제 flyway를 적용할 것이다.

 

궁극적인 적용 목표는 다음과 같다. (환경은 gradle, spring boot, mySql 를 기준으로 한다.)

 

1. DB에 접속해서 table을 직접 건들지 않고, 나이를 포함한 신규 데이터를 저장한다.

2. 동시에 이전 나이가 포함되지 않은 데이터는 나이 필드를 0으로 초기화한다.

3. git으로 관리할 것이기 때문에, 해당 작업을 파일로 관리할 수 있어야 한다.

 

1. 의존성 추가 : build.gradle

 

dependencies {
    implementation('org.flywaydb:flyway-core:6.4.2')
}

 

2. 어플리케이션 설정 추가 : application.properties

 

#data source 설정 / 본인 환경에 맞게 수정해주세요.
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:13306/flyway
spring.datasource.username=ecsimsw
spring.datasource.password=1234

#flyway 설정
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.flyway.enabled=true
spring.jpa.generate-ddl=false

#Spring boot 2 이상의 경우 아래 설정 추가
spring.flyway.baselineOnMigrate = true

 

3. init 파일 추가 : resources/db/migration/V1__init.sql

 

flyway를 등록하려는 시점의 db 스키마 구조를 입력해야 한다. 이 파일을 기준으로 flyway가 DB 버전 관리를 하게 된다. 지우면 안 된다.

resources/db/migration/V1__init.sql, 파일 경로와 파일명을 똑같이 해야 한다. 

# 이전 table를 지우고
drop table if exists sample_entity;

# 초기 스케마를 정의하고
create table sample_entity(
    id   bigint auto_increment,
    name varchar(255),
    primary key (id)
);

# history를 추가한다.
INSERT into sample_entity (name) values ('ecsimsw');
INSERT into sample_entity (name) values ('코기');
INSERT into sample_entity (name) values ('김진환');

 

4. 스키마 구조 변경 사항 파일 추가 : resources/db/migration/V2__add_age.sql

 

이렇게 flyway를 적용한 시점(아직 age 사용 x)에서 작업을 하다가, 스키마 구조가 변경되는 상황(age 필요)이 생겼다고 하자. 이때 변경 사항을 파일에 저장하는 것이다. 이때 파일명은 flyway의 규칙을 따라야 한다. 어렵다면 일단 'V {숫자}__{설명}. sql' 정도만 알고 넘어가자. 

 

https://flywaydb.org/documentation/concepts/migrations.html

 

다시 예제로 돌아와 'resources/db/migration/V2__add_age.sql'을 작성할 것이다. 파일에 스키마 변경 사항을 작성해주는 것이다. 마찬가지로 파일 위치와 파일명에 유의하길 바란다.

 

ALTER TABLE sample_entity ADD COLUMN age integer default 0

 

이렇게 하고 다시 age를 포함한 데이터를 추가하면 이제는 에러 없이 잘 들어감을 확인할 수 있다. 이제는 개발 도중 스키마가 변경되면 직접 배포 서버의 DB를 들어가 스키마를 변경하지 않고,  변경 사항을 코드로 관리할 수 있게 된 것이다.

 

5. flyway_schema_history 확인하기

 

마지막으로 DB에 저장된 flyway_schema_history를 확인한다. V1__init.sql과 V2__add_age.sql이 히스토리로 저장된 것을 확인할 수 있다.

 

 

추가 : 초기 설정 도중 아래의 에러를 만난다면 : Detected failed migration to version 1 (init)

org.springframework.beans.factory.BeanCreationException: 
  Error creating bean with name 'flywayInitializer' defined in class path resource ...
  Invocation of init method failed; 
  nested exception is java.lang.reflect.InvocationTargetException

Caused by: java.lang.reflect.InvocationTargetException: null

Caused by: org.flywaydb.core.api.FlywayException: Validate failed: 
Detected failed migration to version 1 (init)

 

설정 중 잘못된 flyway history 파일이 생겼을 수 있다. 내 경우에는 DB에 직접 접속해서 flyway_schema_history를 직접 제거하고 다시 실행시키니 해결되었다.

 

Comments