티스토리 뷰
Flyway 란?
Flyway is an open-source database migration tool.
Flyway 공식 홈페이지를 보면 Flyway를 데이터베이스 마이그레이션 도구 라고 소개한다.
데이터베이스 마이그레이션 도구란?
데이터베이스 마이그레이션 도구는 버전 관리를 통해 여러 버전의 데이터베이스 스키마와 데이터 변경을 추적할 수 있고, 롤백 기능으로 변경 사항을 쉽게 되돌릴 수 있으며, 테스트 작업을 지원하여 데이터베이스 변경 사항을 안전하게 검증하고 적용할 수 있도록 도와줍니다
Flyway를 사용하면서 발생한 문제점
Flyway에는 규칙이 몇가지 존재하는데
- migration 되었던 sql 파일이 변경될 경우 체크섬 검증이 실패하며, migration 이 수행되지 않습니다.
- sql 파일 네이밍 규칙이 존재합니다.
팀에서 Flyway를 처음 도입하고 관련 규칙을 모두 이해하고 있었지만, 여러 가지 이유로 잘못된 SQL 파일이 merge되서 마이그레이션에 실패하는 경우가 자주 발생했습니다.
문제는 마이그레이션 실패 시 'repair' 명령을 사용하여 복구하고 'success' 상태를 변경하는 작업이 필요한데, 해당 작업을 수행하기 전까지 서버가 다운타임이 발생하면서 많은 불편함이 있었습니다.
그래서 pr이 merge 되기 전에 migration 검증의 필요성을 느꼈습니다.
Flyway Validate
name: DB CI with Flyway
on:
workflow_dispatch:
pull_request:
branches: ["backend"]
permissions:
contents: read
jobs:
build:
runs-on: development
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Create flyway.conf
run: |
touch flyway.conf
echo "flyway.url=${{ secrets.DEV_DATASOURCE_URL }}" >> flyway.conf
echo "flyway.user=${{ secrets.DEV_DATASOURCE_USERNAME }}" >> flyway.conf
echo "flyway.password=${{ secrets.DEV_DATASOURCE_PASSWORD }}" >> flyway.conf
echo "flyway.encoding=UTF-8" >> flyway.conf
echo "flyway.outOfOrder=true" >> flyway.conf
echo "flyway.locations=filesystem:src/main/resources/db/migration" >> flyway.conf
echo "flyway.validateOnMigrate=true" >> flyway.conf
echo "flyway.baselineVersion=0" >> flyway.conf
working-directory: ./backend
- name: flywayValidate
run: ./gradlew -Dflyway.configFiles=flyway.conf flywayValidate --stacktrace
working-directory: ./backend
Execution failed for task ':flywayValidate'.
14> Error occurred while executing flywayValidate
15 Validate failed: Migrations have failed validation
16 Detected resolved migration not applied to database: 5.
17 To fix this error, either run migrate, or set -ignoreMigrationPatterns='*:pending'.
18 Need more flexibility with validation rules? Learn more: https://rd.gt/3AbJUZE
19
그래서 처음 github action을 활용해서 Validate 스크립트를 작성했지만
위와 같은 예외 메시지와 함께 flywayValidate은 작동하지 않았습니다.
왜냐하면 FlywayValidate는 "적용된 마이그레이션을 검증" 하기때문에
pending상태(새로운 SQL)가 존재할경우 에러를 반환합니다.
Flyway Dry Runs
조금더 찾아보니 Dry Runs을 활용해서 미리 변경사항을 검증 가능하지만
해당 기능은 유료 플랜이기때문에 다른 방법이 필요했다.
Mysql Dump 후 실제 Mysql에 마이그레이션
- name: MYSQL 컨테이너 실행
env:
DOCKER_HEALTHCHECK_OPTIONS: --health-cmd='mysql -uroot -proot -e "show databases;"' --health-interval=5s --health-timeout=5s --health-retries=3
# 도커 컨테이너 실행 후 MySQL 서버가 실행되어 접속 가능한지까지를 확인한다.
# 접속까지 가능해야 healthy 상태이다.
timeout-minutes: 5
# 5분 안에 접속 가능하지 않으면 이 단계를 타임아웃 처리한다.
run: |
sudo docker run --rm --name $MYSQL_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=root -P \
${{ env.DOCKER_HEALTHCHECK_OPTIONS }} \
-d mysql:8.0
while [ "`sudo docker inspect -f {{.State.Health.Status}} $MYSQL_CONTAINER_NAME`" != "healthy" ]
do
echo "status: " `sudo docker inspect -f {{.State.Health.Status}} $MYSQL_CONTAINER_NAME`
sleep 5
done
- name: 개발 DB 덤프
...
- name: 덤프를 MySQL 컨테이너로 임포트
...
- name: flyway.conf 생성
...
- name: flywayMigrate 실행
...
- name: MYSQL 컨테이너 제거
if: always()
shell: bash {0}
run: |
sudo docker rm -f $MYSQL_CONTAINER_NAME
마지막으로 고려한 방법은 운영 중인 MySQL 데이터베이스에서 일부 데이터를 복제하여 테스트 데이터베이스에서 마이그레이션을 실행하는 방식입니다.
그러나 이 방식 역시 얼마 되지 않아 장애가 발생하는 문제가 있었습니다.
No space left on device
몇일뒤 장치에 남은 공간이 없다는 에러로 Actions가 실패하는 문제가 생겼습니다.
실제로 서버에서는 디스크 용량을 100% 사용중이였습니다.
원인을 확인하니 Docker Volume이 제거되지 않는 문제였습니다.
run --rm 옵션을 사용하면 컨테이너가 제거될 때 컨테이너와 연결된 익명 볼륨도 제거된다고 나와있는데 왜 제거가 되지않을까?
이유는 Mysql 컨테이너를 실행할때 생기는 볼륨이 익명 볼륨이 아니기 때문입니다.
그렇기 때문에 컨테이너를 종료할때 연결된 볼륨또한 모두 지워주는 옵션이 필요했습니다.
Mysql Dump 후 실제 Mysql에 마이그레이션 최종 스크립트
name: DB CI with Flyway
on:
workflow_dispatch:
pull_request:
branches: ["backend"]
permissions:
contents: read
jobs:
validate:
runs-on: development
defaults:
run:
working-directory: ./backend
env:
MYSQL_CONTAINER_NAME: temp-mysql
SOURCE_DB_HOSTNAME: ip-192-168-2-43.ap-northeast-2.compute.internal
SOURCE_DB_PORT: 3306
SOURCE_DB_SCHEMA: digginroom_dev
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: MYSQL 컨테이너 실행
env:
DOCKER_HEALTHCHECK_OPTIONS: --health-cmd='mysql -uroot -proot -e "show databases;"' --health-interval=5s --health-timeout=5s --health-retries=3
# 도커 컨테이너 실행 후 MySQL 서버가 실행되어 접속 가능한지까지를 확인한다.
# 접속까지 가능해야 healthy 상태이다.
timeout-minutes: 5
# 5분 안에 접속 가능하지 않으면 이 단계를 타임아웃 처리한다.
run: |
sudo docker run --name $MYSQL_CONTAINER_NAME -e MYSQL_ROOT_PASSWORD=root -P \
${{ env.DOCKER_HEALTHCHECK_OPTIONS }} \
-d mysql:8.0
while [ "`sudo docker inspect -f {{.State.Health.Status}} $MYSQL_CONTAINER_NAME`" != "healthy" ]
do
echo "status: " `sudo docker inspect -f {{.State.Health.Status}} $MYSQL_CONTAINER_NAME`
sleep 5
done
- name: 개발 DB 덤프
env:
DUMP_OPTIONS: --single-transaction --no-tablespaces
# 테이블 락킹 권한과 테이블 스페이스 열람 권한 (PROCESS 권한) 없이 덤프할 수 있다.
run: |
sudo docker exec $MYSQL_CONTAINER_NAME sh -c ' \
mysqldump \
-u ${{ secrets.DEV_DATASOURCE_USERNAME }} -p${{ secrets.DEV_DATASOURCE_PASSWORD }} \
-h ${{ env.SOURCE_DB_HOSTNAME }} -P ${{ env.SOURCE_DB_PORT }} \
-B ${{ env.SOURCE_DB_SCHEMA }} \
${{ env.DUMP_OPTIONS }} \
--where="1 limit 100" \
' > dump.sql
- name: 덤프를 MySQL 컨테이너로 임포트
run: |
sudo docker exec -i $MYSQL_CONTAINER_NAME sh -c ' \
exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" \
' < dump.sql
- name: flyway.conf 생성
run: |
DOCKER_PORT=`sudo docker inspect --format="{{(index (index .NetworkSettings.Ports \"3306/tcp\") 0).HostPort}}" $MYSQL_CONTAINER_NAME`;
echo "MySQL Container Port: $DOCKER_PORT";
touch flyway.conf
echo "flyway.url=jdbc:mysql://127.0.0.1:$DOCKER_PORT/$SOURCE_DB_SCHEMA" >> flyway.conf
echo "flyway.user=root" >> flyway.conf
echo "flyway.password=root" >> flyway.conf
echo "flyway.encoding=UTF-8" >> flyway.conf
echo "flyway.outOfOrder=true" >> flyway.conf
echo "flyway.locations=filesystem:src/main/resources/db/migration" >> flyway.conf
- name: flywayMigrate 실행
run: ./gradlew -Dflyway.configFiles=flyway.conf flywayMigrate --stacktrace >> $GITHUB_STEP_SUMMARY
- name: MYSQL 컨테이너 제거
if: always()
shell: bash {0}
run: |
sudo docker rm -v -f $MYSQL_CONTAINER_NAME
마무리
실제 운영 중인 Mysql에서 일부 데이터를 복제한 test db에서 마이그레이션을 실행하는 방식으로
flyway로 인한 장애 없이 안정적으로 서비스를 진행할 수 있게 되었습니다.
- Total
- Today
- Yesterday
- @ElementCollection
- defer-datasource-initialization
- HandlesTypes
- CreationTimestamp
- 유저 시나리오
- DispatcherServletInitializer
- FormProperty
- entity 검증
- @FormProperty
- CreatedDate
- 구글 OpenID
- HTTPInterface
- 유저 스토리
- User Scenario
- @Converter
- dto 위치
- java 17
- dto 검증
- WebFlux 의존성
- BasicBinder
- JPA SQL Injection
- feignClient
- ServletContainerInitializer
- org.springframework:spring-webflux
- Spring Boot 3
- setDateFormat
- Attribute Converter
- ValidateException
- 레이어드 아키텍처
- 구글 소셜로그인
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |