프로젝트 초기에는 Hibernate의 ddl-auto
옵션과 data.sql
파일을 활용해 데이터베이스 스키마를 관리했습니다.
그러나 애플리케이션이 EC2 인스턴스 두 대로 확장되면서 스키마 버전 관리 및 데이터 일관성 유지 등 고려해야할 포인트가 늘어났습니다. 이를 해결하기 위해 데이터베이스 마이그레이션 도구인 Flyway를 도입하기로 결정했습니다.
Flyway는 데이터베이스 변경 이력을 버전으로 관리하고 다중 인스턴스 환경에서도 일관된 마이그레이션을 적용할 수 있도록 도와주는 도구입니다.
간단하니 금방 끝나겠지.. 하며 시작했던 작업은 예상치못한 실수로 인해 큰 난관을 겪었습니다 😇
build.gradle
에 의존성을 추가해주었습니다. // flyway
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FlywayConfig {
@Bean
public FlywayMigrationStrategy flywayMigrationStrategy() {
return flyway -> {
flyway.repair();
flyway.migrate();
};
}
}
flyway.repair();
는 스키마 변경 이력과 실제 스키마 간의 불일치를 해결하기 위한 설정입니다.→ 여기서 repair 옵션 이해를 잘 해야 합니다.. 안 그러면 저처럼 삽질을 오랜시간 해야할 수 있습니다 🧹
공식문서에는 다음과 같이 설명되어있는데요!
• Realign the checksums, descriptions and types of the applied migrations with the ones of the available migrations
(한글 번역: 적용된 마이그레이션의 체크섬, 설명 및 유형을 사용 가능한 마이그레이션의 체크섬, 설명 및 유형으로 재정렬합니다.)
사실 읽어봐도 잘 이해가 되지 않습니다.. checksum이 뭔지부터..
먼저 flyway를 도입 후 옵션을 설정하면 flyway_schema_history
라는 테이블이 생깁니다.
그리고 flyway_schema_history
의 column을 보면 checksum 이 있습니다.
→ checksum 은 그저 column 이름입니다.
checksum 은 보시다시피 일련의 난수인데, 파일(내용까지 포함한) 고유의 hash 값 같은 것이라 추측하고 있습니다. 그래서 파일의 내용이 변경되면 checksum의 난수도 변경이 됩니다.
flyway는 checksum 이 이전과 다르면 파일의 변경점이 있다고 인식하는 거죠.
그런데 여기서, repair 옵션 설정을 해주지 않으면 flyway는 이 checksum 이 달라졌다고 에러를 냅니다.
반대로 repair 설정을 하면 에러를 내지않고 변경사항을 알아서 반영을 해주는 것입니다.
(repair 설정을 해 놓는다는 것은 스키마 파일의 변경을 허용한다는 것이니 주의하며 사용해야겠죠?)
아무튼 그렇고
application.yml
설정도 해줍니다. spring:
flyway:
enabled: true # flyway 활성화 - 로컬에서는 h2 쓰니 false 로 꺼뒀음
baseline-on-migrate: true # flyway_schema_history 테이블이 없으면 생성
baseline-version: 0 # 해당 version 부터 (0으로 두어야 1부터 읽는다.)
jpa:
defer-datasource-initialization: false # Flyway가 먼저 마이그레이션을 적용한 후에 JPA 실행
ddl-auto: validate
resources/db/migration 경로 안에 V1__name.sql
의 형식으로 작성해야 합니다.
스키마 변경 사항이 생기면 V2, V3 ~ 로 해당 경로 밑에 추가해 나가면 됩니다.
그런데 전 여기서 사고를 쳤습니다.
테이블 간 제약 조건의 이름을 모두 같게 설정했습니다. (되는 줄 알았어요 gpt 선생님이 된다고 했는데)
예를 들어 아래처럼 discussion
과 discussion_comment
테이블의 member_id
제약 조건 이름 모두를 fk_member
로 설정해버린 것이죠.
CREATE TABLE discussion (
id BIGINT AUTO_INCREMENT,
title VARCHAR(255),
content TEXT NOT NULL,
mission_id BIGINT,
member_id BIGINT NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
CONSTRAINT pk_discussion PRIMARY KEY (id),
CONSTRAINT fk_member FOREIGN KEY (member_id) REFERENCES member(id), // 여기
CONSTRAINT fk_mission FOREIGN KEY (mission_id) REFERENCES mission(id)
);
CREATE TABLE discussion_comment (
id BIGINT AUTO_INCREMENT,
content TEXT NOT NULL,
discussion_id BIGINT NOT NULL,
member_id BIGINT NOT NULL,
parent_comment_id BIGINT,
deleted_at TIMESTAMP(6),
created_at TIMESTAMP(6) NOT NULL,
CONSTRAINT pk_discussion_comment PRIMARY KEY (id),
CONSTRAINT fk_discussion FOREIGN KEY (discussion_id) REFERENCES discussion(id),
CONSTRAINT fk_member FOREIGN KEY (member_id) REFERENCES member(id), // 여기
CONSTRAINT fk_discussion_comment FOREIGN KEY (parent_comment_id) REFERENCES discussion_comment(id)
);
때문에 flyway 가 V1__init.sql
를 실행하다 에러가 났습니다. (서버가 터졌습니다.)
그런데 문제는 ‘실행하다’ 에러가 난 것입니다.
V1__init.sql
의 제약 조건 이름을 같게 설정하기 전 line 까지만 실행이 되었고, 그 밑 line 은 전부 실행이 안되었습니다.
CREATE TABLE member (
id BIGINT AUTO_INCREMENT,
email VARCHAR(255),
provider VARCHAR(255) NOT NULL CHECK (provider IN ('GITHUB')),
social_id BIGINT NOT NULL,
name VARCHAR(255) NOT NULL,
image_url VARCHAR(255) NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
CONSTRAINT pk_member PRIMARY KEY (id)
);
CREATE TABLE mission (
id BIGINT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
thumbnail VARCHAR(255) NOT NULL,
summary VARCHAR(255) NOT NULL,
url VARCHAR(255) NOT NULL,
CONSTRAINT pk_mission PRIMARY KEY (id)
);
CREATE TABLE discussion (
id BIGINT AUTO_INCREMENT,
title VARCHAR(255),
content TEXT NOT NULL,
mission_id BIGINT,
member_id BIGINT NOT NULL,
created_at TIMESTAMP(6) NOT NULL,
CONSTRAINT pk_discussion PRIMARY KEY (id),
CONSTRAINT fk_member FOREIGN KEY (member_id) REFERENCES member(id),
CONSTRAINT fk_mission FOREIGN KEY (mission_id) REFERENCES mission(id)
);
⬆️ 여기까지만 실행이 되었음
아까 flyway_schema_history
의 column 을 보시면, success 라는 이름의 column 이 있습니다.
이 success 는 true(1), false(0) 으로 구분이 되는데요,
flyway 는 flyway_schema_history
의 row 중 success 가 false(0)인 것부터 실행을 합니다. 현재는 (V1__init.sql
)
그래서 V1__init.sql
부터 실행이 다시 되는데.. 이미 실행된 line 에 CREATE TABLE
을 하는 sql이 있었으므로, 그대로 다시 실행시키면 이미 해당 테이블이 있다며 에러가 납니다.
때문에 이미 실행된 CREATE TABLE
sql 에 IF NOT EXISTS
조건을 추가하고, 아래 행 또는 V2__change_ddl.sql
부터 제약 조건 이름을 수정한 정상적인 sql 로 수정하여 추가해줍니다.
→ 수정한 PR 참고!
(사실 drop table flyway_schema_history
을 하고 V1 부터 실행시키면 그만인데 drop 하는 권한이 없었습니다.)
flyway_schema_history
테이블의 success 가 0인 것부터 실행 (V1__init.sql
)V1__init.sql
의 member, mission, discussion 테이블은 이미 create 된 상태IF NOT EXISTS
조건을 추가하고, (여기서 checksum 수정된 상태- repair 작동하여 에러나지 않고 반영하게 함, repair 를 안 할 경우 checksum 이 달라졌다고 에러)IF NOT EXISTS
조건을 추가하지 않으려면.. 방법은 drop table flyway_schema_history
뿐V2__change_ddl.sql
부터 discussion 테이블의 제약조건 DROP
및 다시 ADD
(네이밍 수정)여러분은 부디 제약 조건 이름 중복으로 설정하지 말고(gpt를 믿지말고) flyway 도입 시에 꽃길만 밟으십시오
그래도 이번 삽질로 배운 것도 많아서 좋습니다 👍