프로젝트 중간에 투입이 되면서 Git Pull 당겨 기본적인 셋팅을 하고 실행을 시키니 다음과 같은 오류를 만나게 되었다.
caused by: org.flywaydb.core.api.exception.flywayvalidateexception: validate failed: migrations have failed validation migration checksum mismatch for migration version 20240831 -> applied to database : -886486009 -> resolved locally : 2094648100
Flyway? 아주 생소한 용어로 오류를 찾아보던 중 Flyway를 어느정도 이해하게 되고 프로세스 과정을 파악하게 되었다.
알아두면 유용한 도구라고 생각되었고 오류를 해결한 과정을 기록해보려 한다.
JPA 환경에서 DB에 새로운 칼럼이 추가될때
Entity와 DB는 서버를 구동할 때 맵핑을 한다.
운영중인 애플리케이션의 경우 jpa.hibernate.ddl-auto 옵션은 기본적으로 none으로 Entity에 원하는 칼럼 필드를 추가하고 서버를 재실행 하더라도 바로 DB에 반영이 되지 않는다.
jpa.hibernate.ddl-auto : update 바꾼다면 서버를 재실행할때 추가된 컬럼에 대해 ALTER문이 실행되어 DB에 칼럼이 추가된다.
하지만 update 옵션 설정을 깜짝하고 기존에 존재하던 칼럼이 삭제되는 경우 매우 중요한 칼럼이 삭제가 된다면 어떻게 될까? 그리고 배포까지 해버리면 어떻게 될까? 큰 문제를 가져올 수 있다.
그러면 새로운 칼럼을 추가하거나 칼럼에 대한 변동사항이 있다면 직접 DB에 칼럼을 변동사항을 적용해주어야 할까?
위의 그림처럼 서버가 개발, 운영 서버 등 여러개 있다면 일일히 스키마 수정을 위한 DDL을 실행해주어야 하는가?
기존 로컬이나 개발에서 JPA사용하며 데이터에 문제가 생기거나 테이블 구조가 마음에 안들면 모두 밀어버리고 다시 CREATE해서 사용해도 무방했다. 하지만 운영중인 서버에서 DROP을 자주 사용할 수 있을까? 매우 위험한 명령어이다.
또한 소스코드는 GIT, SVN 등을 통해 형상관리가 되고 있는데 테이블은 형상관리가 되고 있는가? Human error에 대한 문제는 어떻게 잡고있을까?
위와 같은 문제를 해결 할 수 있는 도구가 바로 Flyway이다.
Flyway란 무엇인가?
Flyway는 오픈소스 데이터베이스 마이그레이션 도구이다.
마이그레이션이란 이동하는 것을 말한다.
코드로 작성한 클래스를 실제 데이터베이스에 테이블로 옮기는 과정을 말한다.
Models -> Migrations -> Database
하지만 Flyway에서는 Migrations(마이그레이션)을 다음과 같이 정의하고 있다.
With Flyway all changes to the database are called migrations.
정리하자면,
Flyway는 데이터베이스 마이그레이션 툴로 모든 변경사항을 추적하고, 업데이트나 롤백을 보다 쉽게 할 수 있는 도구이며 DB 형상관리를 위한 도구이다.
그럼 Flyway에 대해서 더 자세히 알아보도록 하자.
Flyway의 7가지 명령어
Flyway는 총 7가지 특징을 나눠볼 수 있다.
- Migrate : 데이터베이스의 변경
- Baseline : 비어있지 않은 데이터베이스에서 flyway을 적용할 때 사용하는 키워드
- Info : 데이터베이스 변경이력을 저장
- Repair : 스키마 기록 테이블을 다시 복구 할 수 있다.
- Validate : flyway에 적용할때 사용 가능한 마이그레이션에 대해 적용된 마이그레이션의 유효성을 검사
- Undo : 최근에 적용됐던 Migration 파일을 적용하지 않은 상태로 되돌리는 키워드
- Clean : 데이터베이스를 깨끗이 지운다는 의미
1. Migrate
앞선 말한것과 같이 데이터베이스 변경사항을 추적하여 적용합니다.
flyway을 적용하여 실행하게 되면 history테이블이 자동생성된다.
현재 데이터베이스 상태는 version1이고 V2_Changes.sql을 이용해 마이그레이션을 적용한 결과가 오른쪽 그림이다.
2개의 빨간색 테이블이 추가로 생성된 것을 볼 수 있다.
2. BaseLine
비어있지 않은 데이터베이스에서 flyway을 적용할 때 사용하는 키워드
baseline version 까지의 모든 데이터베이스 마이그레이션을 제외하고 그 이후부터의 데이터베이스 flyway의 기능을 적용하게 되는데
해당 명령어를 통해 baseline 전까지의 모든 마이그레이션을 무시하게 되어
오래되고 관련이 없을 수 있는 많은 스크립트를 처리하는 오버헤드를 줄일 수 있다.
3. Info
데이터베이스 변경이력을 저장하고 있어 모든 데이터베이스 마이그레이션에 대한 세부 정보 및 상태 정보를 보여준다.
사진에서는 3개의 sql 파일을 테이블로 보여주고 있으며 카테고리, 버전, 설명, 설치 날짜, 성공여부등의 상세한 정보를 확인 할 수 있다.
4. Repair
스키마 기록 테이블을 복구하며 스키마 기록 테이블의 문제를 해결하는 도구로 사용된다.
5. Validate
사용 가능한 데이터마이그레이션에 대해 적용된 마이그레이션의 유효성을 검사합니다.
유효성 검사는 스키마를 안정적으로 재생성하지 못하게 할 수 있는 우발적인 변경 사항을 감지하는데 매우 유용하다.
6. Undo
최근에 적용됐던 Migration 파일을 적용하지 않은 상태로 되돌리는 키워드로
해당 키워드는 flyway Teams 버전에서만 사용이 가능합니다.
7. Clean
clean 명령어는 개발과 테스트에 큰 도움을 주는 키워드로 구성된 스키마를 완전히 삭제하여 효과적으로 새로운 시작을 할 수 있다.
모든 개체(테이블, 뷰, 프로시저 등)가 삭제된다.
7가지 특징을 살펴보면 중간에 .sql 파일을 볼 수 있는데 이 파일은 Migrate 파일로 생성규칙이 존재한다.
Mirgrate 파일 이름 규칙
SQL 파일 이름을 설정하는 방법으로 대표적으로 5가지의 요소를 가지게 된다.
- Prefix - V, U, R 중 하나를 입력하며 V = version, U = undo, R = repeatable
- Version - 버전 정보로 정수, 소수, 날짜 등이 가능하다.
- 파일의 버전은 Unique 한 특성이 있어 같은 버전이 2개가 있다면 오류를 발생시킨다.
- Seperator - __(언더바 2개)
- Description - 파일 설명
- Suffix - 파일 확장자를 나타낸다.
- 마이그레이션 파일의 위치
- main/resources/db/migration로 이 경로에 스크립트 파일을 추가하게 된다.
- 커스텀 위치 : application.yml에서 spring.flyway.locations 옵션의 값을 classpath:db/migration/{다른 경로} 로 변경
- 파일의 버전(unique)
- 새로 적용하려는 파일은 기존에 적용된 파일의 버전보다 높아야 한다.
- 만약 최근에 적용된 마이그레이션 파일이 V1이라고 가정한다면 V0, V2 동시에 적용하려고 할 때 V0 무시되고 V2가 적용된다.
- 새로 적용하려는 파일은 기존에 적용된 파일의 버전보다 높아야 한다.
실습환경구성
- MySQL 8.3.0
- SpringBoot 3.1.5
- JDK 17
- Hibernate 6.2.13
- Flyway 9.16.3
의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.flywaydb:flyway-core' // flyway-core 의존성 추가
implementation 'org.flywaydb:flyway-mysql' // Mysql 8.X 버전, MariaDB라면 해당 종속성 추가 필요
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.mysql:mysql-connector-j'
...
}
DataSource 설정
application.yml 또는 application.properties에 DataSource 관련 설정을 추가한다.
SpringBoot는 DataSource 설정으로 Flyway를 자동으로 연결한다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tablename
username: XXX
password: XXX
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
generate-ddl: true
hibernate:
ddl-auto: validate
flyway:
enabled: true
baseline-on-migrate: true
JPA 설정
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
generate-ddl: true
hibernate:
ddl-auto: validate
ddl-auto 옵션을 validate 로 설정하면, 실제 데이터베이스 스키마와 JPA 엔티티의 구조가 서로 같은지 비교하고, 같지 않다면 어플리케이션을 실행하지 못하도록 한다.
이 옵션을 사용하여 Flyway를 사용했을때 올바르게 데이터베이스 마이그레이션이 진행되었는지 확인해볼것
Flyway 활성화
spring:
# ...
flyway:
enabled: true
JPA 엔티티 구조
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password;
}
최초 마이그레이션 스크립트 작성
최초로 실행될 마이그레이션 스크립트이다.
파일의 경로는 resources/db/migration 이며 파일명은 V1__init.sql로 지정
프로젝트 Run 버튼을 누르고 테이블이 생성되었는지 확인
- flyway_schema_history : 형상관리를 위한 테이블로 script에 버전관련 파일명과 checksum이 들어가 있다.
- member -> 스크립트에 작성된 테이블 생성
Entity에 칼럼 추가 - content 추가
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password;
private String content;
}
재실행
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-10-22T13:41:47.719+09:00 ERROR 38429 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'entityManagerFactory' defined in class path resource
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]:
[PersistenceUnit: default] Unable to build Hibernate SessionFactory;
nested exception is org.hibernate.tool.schema.spi.SchemaManagementException:
Schema-validation: missing column [content] in table [member]
에러가 발생한다 이점에 주목해보자
Schema-validation: missing column [content] in table [member]
ddl-auto를 validation으로 설정하면서 변경된 내역을 반영하지 못하고 있다.
스키마 불일치로 발생하는 에러로 새로운 버전의 마이그레이션 스크립트 파일이 필요하다.
새로운 버전의 마이그레이션 스크립트 작성
V2__add_contact.sql 생성
ALTER TABLE member ADD COLUMN content VARCHAR(255);
프로젝트 RUN 실행
해당 칼럼이 추가되었다. 그리고 history 내역도 확인해보자
v2버전에 대해 로우가 추가되었다.
Versioned Migrations
다시 한 번 강조해보면 Migrations 기능은 Flyway 의 핵심기능이다.
마이그레이션 스크립트의 최신 버전과 현재 데이터베이스의 스키마 버전을 비교하여, 차이점이 있다면 마이그레이션 스크립트를 순차적으로 실행하여 최신 스크미와 격차를 좁혀 나간다.
만약 마이그레이션 버전이 1~0까지 있고 현재 개발환경에서 스키마가 5버전일때, 최신 버전에 대해 마이그레이션을 실행하면 스크립트 6~9 버전이 순차적으로 실행된다. 개발 환경의 최신 스키마가 9버전이면, 아무 스크립트도 실행되지 않는다.
최신 마이그레이션 버전의 숫자보다 작은 숫자의 버전으로 마이그레이션 스크립트를 추가한다면, 그 마이그레이션 스크립트는 무시된다.
예를 들어 최신 버전이 10인데 9를 이후에 추가하는 경우 해당 스크립트는 무시한다.
해당 프로젝트는 flyway v2까지 실행했다. 프로젝트를 다시 실행해보자
Caused by: org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 2
-> Applied to database : -2088626076
-> Resolved locally : -104825358
Either revert the changes to the migration, or run repair to update the schema history.
Need more flexibility with validation rules? Learn more: https://rd.gt/3AbJUZE
FlywayValidateException이 발생하며 다음과 같은 오류메시지가 출력되었다.
Migration checksum mismatch for migration version 2
Flyway는 각 마이그레이션 스크립트 별로 체크섬을 비교하여 유효성 검사를 한다. 따라서, 스키마에 대한 모든 변경은 반드시 새로운 버전의 마이그레이션 스크립트를 추가하는 방법으로 진행해야 한다.
그렇다면 이때 어떻게 해결해야 할까?
checksum 재설정
@Bean
public Flyway flywayBean() {
Flyway flyway = Flyway.configure().baselineOnMigrate(true).load();
flyway.repair();
flyway.migrate();
return flyway;
}
checksum 이용해 유효성을 검사하기 때문에 flyway.migrate() 하기전에 flyway.repair()을 사용하여 체크섬을 재설정해준다.
주의점
형상관리의 장점이 있지만 주의해야할 점은 DB스키마에 변경사항이 생겼을때 이를 Flyway에게 알려주는 스크립트를 잊지 말고 작성해야 한다.
이렇게 DB관리하는 방법을 새롭게 배우게 되었다.
파면 팔수록 개발이란 어렵따,,
Reference
https://www.youtube.com/watch?v=_fgOxPRo8tU
https://www.youtube.com/watch?v=pxDlj5jA9z4
https://www.blog.ecsimsw.com/entry/Flyway%EB%A1%9C-DB-Migration
https://velog.io/@choidongkuen/Flyway%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
https://hudi.blog/dallog-flyway/
https://tecoble.techcourse.co.kr/post/2021-10-23-flyway/