하이요.
이돈이면 이리내입니다.
이번에 데이터베이스 형상관리 툴인 Flyway를 도입해봤는데요,
어떻게 도입했는지 몇자 적어보겠습니다.
때는 지난주 토요일이었습니다.
야무지게 젠킨스를 이용한 자동배포를 구축해놓고 퇴근했죠.
이제 운영 브랜치에 뭐가 merge 될때마다 젠킨스를 통해 운영 서버에 자동배포되는겁니다.
하지만 다음날 아침에 일어나보니...
서버가 안된대요.
오잉? 서버 띄워진 것 확인했으니 잘 돌아갈텐데요!
요청을 보낼때마다 자꾸 예외가 발생하더랩니다.
이런...
왜 예외가 발생했냐면,
밤 사이에 새로 merge된 기능에서, 스키마의 변경이 일어났었는데요!
해당 내용이 데이터베이스에 반영이 되지 않았기 때문입니다.
서비스가 DB를 수정하는 권한을 막아두었거든요.
그래서 JPA가 엔티티가 변경되어도 알아서 수정하지 못했던 거예요.
그래서 황금같은 일요일에!
여우가 캠퍼스로 출근을 해서 뚝딱뚝딱 스키마를 수정하는 고생이 있었읍니다.
얼라리..
자동배포를 했는데, 디비가 바뀔때마다 디비는 손수 수정해줘야한다구?
그럼 merge로 자동배포 해주는 것도 캠퍼스에서만 해줘야하는건가?
너무 구리네!!
그래서 도입했습니다. flyway
데이터베이스 형상관리 툴이래요.
소스코드 형상관리 툴에는 Git이 있잖아요.
그거의 데이터베이스판이라고 생각하면 돼요.
스키마를 변경해야한다면, flyway를 통해 그것을 관리하는거죠.
저희가 flyway를 도입하게 된다면,
일단 위처럼 데이터베이스 수정을 못하거나 까먹는 일을 예방할 수 있을거예요.
배포하는 순간 flyway가 스키마를 알아서 수정해줄테니까요.
또, 테스트 서버와 운영 서버의 스키마 변경사항 관리를 일관성있게 할 수 있게 될거예요.
매번 테스트 서버의 스키마를 수정하고, 그걸 어디다가 저장해놨다가 운영서버의 스키마에도 그 저장내용을 참고해서 수정하긴 번거롭단 말이죠.
build.gradle에 추가해줬어요.
저희는 mysql 8.x를 쓰기 때문에 이렇게 했습니다.
dependencies {
// flyway 추가
implementation 'org.flywaydb:flyway-mysql'
implementation 'org.flywaydb:flyway-core'
}
그리고 프로덕션용 application.properties에 아래 내용도 추가해줬죠.
데이터베이스에 데이터를 CRUD할 수 있는 유저와 별개로, 테이블 스키마를 관리하는 유저도 만들어 환경변수로 쓸 수 있도록 했어요.
#validate로 변경: 데이터베이스의 스키마와 jpa 엔티티가 불일치할때 애플리케이션 실행이 안되도록 함
spring.jpa.hibernate.ddl-auto=validate
#flyway
spring.flyway.enabled=true
spring.flyway.url=${MYSQL_ADDRESS}
spring.flyway.user=${MYSQL_FLYWAY_USERNAME}
spring.flyway.password=${MYSQL_FLYWAY_PASSWORD}
#변경이력테이블이 자동생성되지 않는 경우를 방지
spring.flyway.baseline-on-migrate=true
#마이그레이션 파일 경로 커스텀
spring.flyway.locations=classpath:db/migration/mysql
그리고 중요한 것!
서버에 있는 데이터들을 유지하면서 flyway를 도입 할 것!
그래서 base가 되는 V1__init.sql 파일을 만들어줬어요.
여기에는 현재 데이터베이스 상태와 일치하도록 적어줬습니다.
그리고 이슈도 있었어요.
자꾸 어플리케이션이 제대로 실행이 안되고 이런 오류가 났는데요.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flyway' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Circular depends-on relationship between 'flyway' and 'entityManagerFactory'
이는 application.properties의 어떠한 설정 때문이었어요.
다음 설정이 true로 켜져있었다면 꼭 false로 바꿔줘야해요.
//변경전
spring.jpa.defer-datasource-initialization=true
//변경후
spring.jpa.defer-datasource-initialization=false
테스트 겸, 저희의 Member 클래스에 다음 age 컬럼을 추가해봤어요.
엔티티가 변경됨에 따라 실행되어야 하는 쿼리들은
이런식으로 V{버전}__{설명}.sql 파일을 만들어 추가해줘야 해요.
주의할 점은, 파일 이름을 지을 때 규칙에 맞게 지어야 한다는 거예요.
어쨌든, 어플리케이션을 실행하게 되면 데이터베이스에서 이렇게 변경이력테이블을 볼 수 있답니다!
https://www.youtube.com/watch?v=_fgOxPRo8tU
https://www.youtube.com/watch?v=pxDlj5jA9z4
https://www.blog.ecsimsw.com/entry/Flyway로-DB-Migration
https://ecsimsw.tistory.com/entry/Flyway-DB-마이그레이션-기존-데이터가-있는-경우
https://hudi.blog/dallog-flyway/
https://www.red-gate.com/blog/database-devops/flyway-naming-patterns-matter