험난한 Flyway 도입기

Lily·2024년 11월 4일
1
post-thumbnail

들어가며

프로젝트 초기에는 Hibernate의 ddl-auto 옵션과 data.sql 파일을 활용해 데이터베이스 스키마를 관리했습니다.

그러나 애플리케이션이 EC2 인스턴스 두 대로 확장되면서 스키마 버전 관리 및 데이터 일관성 유지 등 고려해야할 포인트가 늘어났습니다. 이를 해결하기 위해 데이터베이스 마이그레이션 도구인 Flyway를 도입하기로 결정했습니다.

Flyway는 데이터베이스 변경 이력을 버전으로 관리하고 다중 인스턴스 환경에서도 일관된 마이그레이션을 적용할 수 있도록 도와주는 도구입니다.

도입과정

간단하니 금방 끝나겠지.. 하며 시작했던 작업은 예상치못한 실수로 인해 큰 난관을 겪었습니다 😇

  1. 먼저 build.gradle 에 의존성을 추가해주었습니다.
 // flyway
    implementation 'org.flywaydb:flyway-core'
    implementation 'org.flywaydb:flyway-mysql'
  1. repair 옵션 설정을 위해 Config 를 작성해주었습니다.
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(); 는 스키마 변경 이력과 실제 스키마 간의 불일치를 해결하기 위한 설정입니다.
    마이그레이션이 실패한 경우 flyway_schema_history 테이블에 남는 실패 기록을 제거하고, 실제 마이그레이션 파일들이 손실되거나 이동된 경우 flyway_schema_history의 기록을 정리해 일관성을 유지합니다.

→ 여기서 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 이 뭘 의미하냐하면 (사실 삽질하면서 추측한 내용이니 반박시 님 말이 다 맞습니다.)

checksum 은 보시다시피 일련의 난수인데, 파일(내용까지 포함한) 고유의 hash 값 같은 것이라 추측하고 있습니다. 그래서 파일의 내용이 변경되면 checksum의 난수도 변경이 됩니다.
flyway는 checksum 이 이전과 다르면 파일의 변경점이 있다고 인식하는 거죠.
그런데 여기서, repair 옵션 설정을 해주지 않으면 flyway는 이 checksum 이 달라졌다고 에러를 냅니다.
반대로 repair 설정을 하면 에러를 내지않고 변경사항을 알아서 반영을 해주는 것입니다.
(repair 설정을 해 놓는다는 것은 스키마 파일의 변경을 허용한다는 것이니 주의하며 사용해야겠죠?)

아무튼 그렇고

  1. 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
  1. 스키마를 작성합니다.

resources/db/migration 경로 안에 V1__name.sql 의 형식으로 작성해야 합니다.
스키마 변경 사항이 생기면 V2, V3 ~ 로 해당 경로 밑에 추가해 나가면 됩니다.

그런데 전 여기서 사고를 쳤습니다.
테이블 간 제약 조건의 이름을 모두 같게 설정했습니다. (되는 줄 알았어요 gpt 선생님이 된다고 했는데)

예를 들어 아래처럼 discussiondiscussion_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가 어떻게 작동하는지 알아야 합니다.

아까 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 하는 권한이 없었습니다.)

간단하게 정리하면

  1. constraint name 중복 때문에 에러 → success 0
  2. flyway는 flyway_schema_history 테이블의 success 가 0인 것부터 실행 (V1__init.sql)
  3. 하지만 V1__init.sql 의 member, mission, discussion 테이블은 이미 create 된 상태
  4. 때문에 위의 세 테이블은 IF NOT EXISTS 조건을 추가하고, (여기서 checksum 수정된 상태- repair 작동하여 에러나지 않고 반영하게 함, repair 를 안 할 경우 checksum 이 달라졌다고 에러)
  5. IF NOT EXISTS 조건을 추가하지 않으려면.. 방법은 drop table flyway_schema_history
  6. 아래 행 또는 V2__change_ddl.sql 부터 discussion 테이블의 제약조건 DROP 및 다시 ADD (네이밍 수정)
  7. 뒤의 행 실행 (제약 조건 네이밍 바뀐 정상 쿼리)

마무리

여러분은 부디 제약 조건 이름 중복으로 설정하지 말고(gpt를 믿지말고) flyway 도입 시에 꽃길만 밟으십시오

그래도 이번 삽질로 배운 것도 많아서 좋습니다 👍

profile
내가 하고 싶은 거

0개의 댓글

관련 채용 정보