이번 글에서는 운영 환경에서의 데이터베이스 최적화 작업 중 인덱스 설정이 제대로 반영되지 않은 문제를 겪으면서, ddl-auto update 옵션의 치명적인 단점과 그 원인에 대해 정리해보고자 한다.
실제 운영 중인 서비스에서, 한 팀원이 인덱스 설정을 통해 데이터베이스 최적화를 시도한 적이 있었다. 나는 인덱스 설정을 보다 합리적으로 개선하고, 테스트 코드로 최적화 정도를 확인해 보고자 이걸 자세히 파보려 했다. 하지만 테스트를 진행하는 과정에서 실제 운영 데이터베이스에 인덱스 테이블이 존재하지 않음을 가장 먼저 발견했다. (매우 당황스러웠지만.. 블로그를 쓸 생각에 나름 재밌기도?!)
문제의 원인을 찾은 결과, ddl-auto 설정이 update 옵션으로 되어 있었던 것이 큰 패착이라는 사실을 알게되었다.. Hibernate의 update 옵션은 엔티티에 정의된 간단한 변경 사항, 예를 들어 컬럼 추가와 같은 비파괴적인 변경은 자동으로 처리해 주지만, 인덱스와 같이 보다 복잡한 스키마 변경 사항은 제대로 반영하지 못한다. 이로 인해, 우리가 의도한 인덱스 최적화 작업이 실제 데이터베이스에 적용되지 않은 것이다.
따라서 이번 글에서는 Hibernate와 Spring Boot JPA 환경에서 ddl-auto의 개념, 동작 원리, 각 옵션의 특징 및 특히 update 옵션의 단점과 그로 인해 발생했던 문제를 써보려한다. 또한, 이 문제를 해결하기 위해 운영 환경에서 (안정적인 스키마 관리를 위해) validate나 none 옵션을 사용하고, 데이터베이스 마이그레이션 도구인 Flyway를 도입했던 과정까지 자세히 정리해 볼 예정이다.
ddl-auto : update
1. DDL이란?
DDL은 Data Definition Language의 약어로, 데이터베이스의 구조(스키마)를 정의하고 수정하는 데 사용되는 SQL 명령어들의 집합이다. 데이터베이스의 구조를 설계하고 데이터의 저장, 관리, 삭제를 위한 기본 틀을 마련하는 데 중요한 역할을 하며, 주요 명령어로는 CREATE, ALTER, DROP 등이 있다. CREATE 명령어는 새로운 테이블, 인덱스, 뷰 등 데이터베이스 객체를 생성하는 데 사용된다. ALTER 명령어는 기존 데이터베이스 객체의 구조를 변경하는 역할을 하며, 예를 들어 테이블에 새로운 컬럼을 추가하거나 기존 컬럼을 삭제할 때 사용된다. DROP 명령어는 더 이상 필요하지 않은 데이터베이스 객체를 삭제하여 전체 스키마의 효율성을 높이는 데 기여한다.
2. ddl-auto란?
ddl-auto는 Hibernate가 데이터베이스 스키마를 자동으로 관리하도록 하는 설정 옵션이다. 이 설정을 통해 애플리케이션이 시작할 때 엔티티 클래스의 메타데이터를 기반으로 데이터베이스에 필요한 테이블과 컬럼 등을 생성하거나 수정할 수 있다. 일반적으로 application.properties 또는 application.yml 파일에 spring.jpa.hibernate.ddl-auto 속성으로 지정된다.
3. ddl-auto의 동작 원리
Hibernate는 애플리케이션 실행 시 엔티티 클래스를 스캔하여 데이터베이스에 반영되어야 할 정보를 메타데이터 형태로 생성한다.
여기서 메타데이터란 엔티티 클래스에 정의된 속성, 테이블 이름, 연관관계, 외래키 등 데이터베이스 객체의 구조와 관련된 정보를 체계적으로 정리한 데이터이다. 즉, 애플리케이션 내의 객체 모델을 데이터베이스 구조로 변환하기 위한 설계도와 같은 역할을 한다.
스키마는 데이터베이스에 실제 존재하는 테이블, 컬럼, 인덱스, 제약 조건 등 데이터베이스 객체의 구성과 구조를 의미한다. 이는 데이터베이스에 저장된 데이터가 어떻게 구성되고 연결되어 있는지를 정의하는 설계도라고 할 수 있다.
Hibernate가 ddl-auto 기능을 통해 데이터베이스 스키마를 관리하는 과정은 다음과 같이 이루어진다.
3.1. 엔티티 스캔 및 메타데이터 생성
애플리케이션 실행 시 Hibernate는 클래스패스 내의 모든 엔티티 클래스를 스캔한다. 각 엔티티에 정의된 속성, 테이블 이름, 관계(연관관계, 외래키 등)를 분석하여 메타데이터를 생성한다. 이 메타데이터는 데이터베이스에 반영되어야 할 구조 정보를 담고 있으며, 개발자가 정의한 객체 모델을 기반으로 데이터베이스 구조를 설계하기 위한 베이스가 된다.
3.2. 데이터베이스 스키마와 메타데이터 비교
Hibernate는 생성된 엔티티 메타데이터와 현재 데이터베이스에 존재하는 스키마를 비교한다. 이 과정에서는 데이터베이스에 이미 존재하는 테이블, 컬럼, 제약 조건 등의 실제 구조(스키마)와 엔티티에 정의된 이상적인 구조(메타데이터) 간의 차이를 확인한다. 예를 들어, 데이터베이스에 특정 테이블이 존재하는지, 혹은 엔티티에서 새로 추가된 컬럼이 데이터베이스에 반영되어 있는지를 검사한다.
3.3. DDL 명령어 생성
이 단계에서는 엔티티 메타데이터와 데이터베이스 스키마의 차이를 비교한 뒤 어떤 DDL 명령어를 생성할지를 결정한다.
- create 옵션을 사용하면 Hibernate는 기존 스키마를 모두 제거한 후 새로 생성해야 하므로 DROP과 CREATE 명령어를 생성한다.
- update 옵션을 사용하면 이미 존재하는 테이블이나 컬럼은 유지하고 누락된 부분만 보완하기 위해 ALTER TABLE 명령어만 생성한다.
이처럼 각 옵션에 따라 어떤 DDL 명령어들이 생성될지 달라진다.
3.4. SQL 명령어 실행
이 단계는 생성된 DDL 명령어를 실제 데이터베이스에 전달하여 실행하는 단계이다. 옵션에 따라 실행되는 SQL 명령어의 내용은 달라지지만, 실행 자체의 과정은 동일하다. 즉, 이미 생성된 SQL 명령어들을 데이터베이스에 적용하여 스키마를 업데이트해준다.
4. ddl-auto의 주요 옵션
Spring Boot에서는 spring.jpa.hibernate.ddl-auto 속성을 통해 제어할 수 있다. 속성에는 여러 가지 옵션이 있으며, 각 옵션은 데이터베이스 스키마에 대해 다른 동작을 수행한다.
- create: 애플리케이션 실행 시 기존 스키마를 삭제하고, 엔티티에 정의된 구조에 따라 새로운 스키마를 생성한다.
- create-drop: 애플리케이션 시작 시 스키마를 생성하고, 애플리케이션 종료 시 스키마를 삭제한다. 주로 테스트 환경에서 사용된다.
- update: 기존 스키마를 유지하면서 엔티티의 변경 사항만 반영하여 필요한 부분만 업데이트한다. 단, 복잡한 변경(컬럼 삭제, 이름 변경 등)은 제대로 처리되지 않을 수 있다.
- validate: 엔티티와 데이터베이스 스키마의 일치 여부만 검사하며, 스키마 변경은 수행하지 않는다.
- none: Hibernate가 데이터베이스 스키마에 대해 아무런 작업도 수행하지 않는다. 이 경우 개발자가 수동으로 스키마를 관리해야 한다.
5. ddl-auto 사용 시 고려사항
ddl-auto 설정은 초기 개발 단계나 테스트 환경에서는 매우 유용하다. 그러나 프로덕션 환경에서는 데이터베이스 스키마의 변경을 세밀하게 통제할 필요가 있다. 엔티티 변경으로 인한 예기치 않은 데이터 손실이나 스키마 불일치 문제를 방지하기 위해 ddl-auto 대신 별도의 마이그레이션 도구를 사용하도록 권장된다.
또한, ddl-auto의 자동 스키마 업데이트 기능은 단순한 스키마 변경에는 적합하지만, 복잡한 스키마 변경이나 대규모 데이터베이스에서는 수동 검토가 필요하다. 따라서 사용자는 환경에 맞는 적절한 설정과 도구를 선택하여 데이터베이스를 관리해야 한다.
6. 운영 환경에서 update 옵션 사용의 문제점 및 고려사항
개발 서버에서는 ddl-auto의 update 옵션이나 create, create-drop 옵션을 사용하여 빠른 프로토타이핑과 개발 편의성을 누릴 수 있다. 엔티티에 변경 사항이 생기면 자동으로 스키마가 업데이트되어 개발 과정의 생산성을 높여준다. 그러나 실제 운영 서비스에서는 당연히 create나 create-drop 옵션은 쓰지 않는다. 문제는 편리함 때문에 update 옵션을 운영 환경에서 사용하는 경우이다. (나의 패착이었다..)
운영 서비스에서 update 옵션 사용의 문제점은 다음과 같다.
- 제한된 스키마 변경 지원
update 옵션은 컬럼 추가와 같이 단순한 변경은 자동으로 처리하지만, 컬럼 삭제, 이름 변경, 데이터 타입 변경 등 복잡한 스키마 변경은 제대로 지원하지 않는다. 이로 인해 엔티티와 실제 데이터베이스 스키마 간의 불일치가 발생할 수 있다. - 예측 불가능한 결과
update 옵션은 변경된 엔티티를 기반으로 누락된 부분만 보완하기 때문에, 의도치 않은 컬럼 잔존이나 불필요한 데이터 구조가 남을 위험이 있다. 이러한 미세한 차이가 누적되면 전체 스키마에 영향을 미쳐 예기치 못한 결과를 초래할 수 있다. - 데이터 무결성 및 안정성 문제
스키마 업데이트 과정에서 기존 데이터와의 충돌이나 정합성 문제가 발생할 수 있다. 운영 환경에서는 데이터 무결성이 매우 중요한데, update 옵션으로 인한 예기치 않은 변경은 데이터 손실이나 서비스 장애로 이어질 위험이 크다. - 복구 및 문제 해결의 어려움
update 옵션을 사용하여 발생한 스키마 변경 오류나 문제는 원인을 파악하고 복구하는 과정이 복잡하다. 운영 서비스에서는 신속한 문제 해결이 필요한데, update 옵션으로 인한 문제는 복구 시간이 길어질 수 있다.
따라서 이러한 문제들 때문에 운영 환경에서는 update 옵션 대신 validate나 none 옵션을 사용한다.
- none 옵션은 Hibernate가 스키마에 대해 아무런 변경 작업을 수행하지 않도록 하여, 데이터베이스 마이그레이션 도구(Flyway, Liquibase 등)를 사용하여 스키마 변경을 수동으로 관리하게 한다.
- validate 옵션은 엔티티와 데이터베이스 스키마의 일치 여부를 단순히 검사하여, 스키마가 올바르게 구성되어 있는지 확인할 수 있다. 이를 통해 애플리케이션 실행 전, 스키마에 문제가 없는지 검증할 수 있다. (이 또한 데이터베이스 마이그레이션 도구로 스키마 변경을 수동으로 관리한다.)
따라서 운영 환경에서는 update 옵션의 사용을 지양하고, 안정적이고 예측 가능한 데이터베이스 관리를 위해 validate 또는 none 옵션을 선택한 후, 별도의 마이그레이션 도구를 사용하는 것이 좋다.
실제로 나의 경우엔 운영 환경에서 update 옵션을 사용한 결과, 인덱스 테이블이 생성되지 않는 문제가 발생했다. (위 사진은 개발 환경에서 db에 인덱스 테이블이 생긴 걸 가져왔다. 원래는 사진처럼 테이블이 보여야 하는데 실제 운영 db에서 해당 쿼리를 쳤을 때는 아무것도 나오지 않았다..) 이는 update 옵션이 단순히 엔티티에 정의된 변경 사항 중 컬럼 추가와 같은 비파괴적인 작업만을 자동으로 반영하도록 설계되어 있기 때문이다. 복잡한 스키마 변경, 예를 들어 인덱스와 같이 보다 정교한 데이터베이스 객체의 변경은 Hibernate가 안전성을 우선시하는 정책 때문에 제대로 처리하지 못한다고 한다. 이처럼 update 옵션은 예기치 않은 스키마 불일치를 초래할 수 있으며, 인덱스와 같은 최적화 작업이 실제 운영 데이터베이스에 반영되지 않아 데이터 무결성과 성능 측면에서 심각한 문제를 일으킬 위험이 있다.
또한, 내 개발 환경에서는 update 옵션 외의 경우에 변경 사항들이 잘 적용되어 별다른 오류 없이 스키마가 변경되는 경우가 많아 문제를 발견하기가 매우 어려웠다. 이러한 환경적 차이로 인해 운영 환경에서 발생하는 오류를 사전에 파악하기가 쉽지 않다. 이러한 이유로 운영 환경에서는 update 옵션 대신 validate 또는 none 옵션을 사용하고, 별도의 데이터베이스 마이그레이션 도구(Flyway, Liquibase 등)를 도입하여 스키마 변경을 명확하고 안정적으로 관리하는 것이 바람직하다.
Flyway
Flyway는 데이터베이스 마이그레이션 도구로, 데이터베이스 스키마의 버전 관리를 단순하고 명확하게 수행할 수 있도록 돕는다. 기본적으로 SQL 스크립트나 Java 코드를 이용하여 데이터베이스 구조 변경 내역을 버전별로 관리하며, 애플리케이션 배포 시점에 데이터베이스 스키마를 최신 상태로 유지할 수 있도록 해준다.
1. Flyway의 특징과 장점
- 버전 관리 기반 마이그레이션
Flyway는 데이터베이스의 스키마 변경 내역을 버전 번호가 붙은 마이그레이션 파일로 관리한다. 각각의 마이그레이션 스크립트는 특정 버전을 나타내며, 데이터베이스에 적용된 버전과 비교하여 미적용된 변경 사항을 자동으로 순차 적용한다. 이를 통해 여러 개발자나 팀에서 동시에 작업하더라도 일관된 스키마 변경을 보장할 수 있다. - SQL 스크립트와 Java 코드 지원
대부분의 마이그레이션은 SQL 스크립트로 작성되지만, 복잡한 로직이 필요한 경우 Java 기반의 마이그레이션도 지원한다. SQL 스크립트를 사용하면 데이터베이스에 직접 명령어를 전달할 수 있어, 복잡한 스키마 변경이나 데이터 변환 작업을 명확하게 기술할 수 있다. - 다양한 데이터베이스 지원
Flyway는 MySQL, PostgreSQL, Oracle, SQL Server 등 다양한 데이터베이스 시스템을 지원한다. 이는 플랫폼에 구애받지 않고 일관되게 마이그레이션 전략을 적용할 수 있게 해 주기 때문에 여러 종류의 데이터베이스를 사용하는 환경에서도 유용하다. - 간편한 통합 및 설정
Flyway는 Spring Boot와 같은 프레임워크와 쉽게 통합된다. Spring Boot의 경우, 의존성 추가와 간단한 설정만으로 애플리케이션 시작 시 자동으로 마이그레이션을 실행할 수 있다. 이를 통해 별도의 관리 작업 없이도 데이터베이스 스키마가 최신 상태로 유지된다. - 신뢰성과 확장성
Flyway는 간단하면서도 강력한 마이그레이션 도구로, 한 번의 마이그레이션 실패 시 롤백 기능을 제공하지는 않지만, 안정적인 마이그레이션 파일 관리를 통해 반복 가능한 배포와 신뢰성 있는 데이터베이스 관리 환경을 구축할 수 있다. 모든 마이그레이션 이력을 테이블에 기록하여 언제든지 현재 스키마의 상태를 확인할 수 있고, 필요시 수동으로 조치할 수 있다.
왜 Flyway를 도입해야 하는가?
운영 환경에서는 데이터베이스 스키마의 변경이 매우 민감하며, 한 번의 잘못된 변경이 전체 시스템의 안정성과 데이터 무결성에 큰 영향을 미칠 수 있다. Flyway는 이러한 위험을 최소화하기 위해, 변경 이력을 명확하게 관리하고, 순차적으로 적용되는 마이그레이션 파일을 통해 예측 가능한 스키마 업데이트를 보장하며, 여러 환경(개발, 테스트, 운영) 간의 스키마 일관성을 유지할 수 있도록 돕는다.
물론, 데이터베이스 마이그레이션 도구는 Flyway만 있는 것은 아니다. 가장 대표적인 도구로는 Liquibase도 있다. Liquibase는 높은 유연성과 다양한 포맷(XML, JSON, YAML 등)을 지원하며 강력한 롤백 기능을 제공하는 등 여러 장점을 갖추고 있다. 하지만, Liquibase는 강력한 기능을 제공하는 대신 설정과 사용 방식이 그만큼 복잡하다. 다양한 포맷(XML, JSON, YAML)을 지원하다 보니 마이그레이션 파일 작성 및 유지 관리가 번거로워질 수 있고, Spring Boot와의 통합에서도 추가적인 설정이 많다. 따라서 현재 나에겐 Flyway가 더 적합하다고 판단되었다.
2. flyway_schema_history 테이블의 컬럼
Flyway를 적용하면 가장 먼저 데이터베이스에 마이그레이션 이력을 기록할 테이블이 생성된다. 기본적으로 이 테이블의 이름은 flyway_schema_history이다.(이전 버전에서는 schema_version으로 불리기도 했다.) 이 테이블은 Flyway가 실행한 모든 마이그레이션의 정보를 체계적으로 관리하기 위해 사용된다.
- installed_rank: 각 마이그레이션이 적용된 순서를 나타내는 순번이다.
- version: 마이그레이션 파일에 명시한 버전 번호로, 예를 들어 V1, V2 등이 기록된다.
- description: 마이그레이션 파일 이름에 포함된 설명 부분이다. 예를 들어 V1__Initial_setup.sql의 경우 'Initial setup'이 기록된다.
- type: 마이그레이션의 유형을 나타내며, SQL이나 JDBC(Java 기반 마이그레이션 등)를 구분한다.
- script: 실행된 마이그레이션 파일의 이름을 기록하여, 어떤 스크립트가 적용되었는지 확인할 수 있다.
- checksum: 해당 마이그레이션 파일의 체크섬을 저장하여, 나중에 파일이 변경되었는지 검증하는 데 사용된다.
- installed_by: 마이그레이션을 실행한 사용자의 정보를 기록한다.
- installed_on: 마이그레이션이 실행된 시각을 저장한다.
- execution_time: 마이그레이션 스크립트 실행에 소요된 시간을 밀리초 단위로 기록한다.
- success: 해당 마이그레이션이 성공적으로 실행되었는지 여부를 나타내는 플래그이다.
3. Flyway 마이그레이션 종류
Flyway에서 지원하는 주요 마이그레이션 종류는 다음과 같다. 나는 버전 마이그레이션을 활용해서 관리했다. (문제가 생기거나, 기존 것을 변경하지 않는 업데이트일 경우 버전 마이그레이션을 가장 많이 쓸 것이다.)
- 버전 마이그레이션 (Versioned Migrations)
파일명에 'V' 접두사가 붙은 마이그레이션으로, 순차적인 버전 번호를 통해 스키마 변경 이력을 관리한다. - 반복 마이그레이션 (Repeatable Migrations)
파일명에 'R' 접두사가 붙은 마이그레이션으로, 파일 내용이 변경될 때마다 재실행되어 지속적으로 스키마를 업데이트할 수 있다. - Undo 마이그레이션 (Undo Migrations)
Flyway Teams edition에서 제공되는 기능으로, 'U' 접두사가 붙은 파일을 통해 이전 버전으로 롤백할 수 있다.
4. Flyway의 동작 방식
- 마이그레이션 파일 준비
개발자는 특정 형식에 맞는 SQL 또는 Java 마이그레이션 파일을 작성한다. 파일명에는 반드시 버전 번호와 설명이 포함되어야 하며, 예를 들어 V1__Initial_setup.sql, V2__Add_index_courses.sql과 같이 이름을 작성하면 된다. - 버전 확인 및 적용
애플리케이션이 실행될 때, Flyway는 데이터베이스 내에 있는 마이그레이션 이력을 기록한 테이블(flyway_schema_history)을 확인한다. 이후, 작성된 마이그레이션 파일 중 아직 적용되지 않은 버전을 찾아 순차적으로 실행한다. - 스키마 업데이트
각 마이그레이션 파일은 데이터베이스 스키마에 대한 변경 작업을 담고 있으며, Flyway는 이를 실행하여 스키마를 업데이트한다. 성공적으로 적용된 마이그레이션은 기록 테이블에 저장되어, 이후 중복 실행되지 않도록 관리된다. - 오류 처리 및 보고
마이그레이션 도중 오류가 발생하면 Flyway는 해당 오류를 보고하고, 배포 프로세스를 중단한다. 이를 통해 스키마의 일관성을 유지하며, 문제 발생 시 개발자가 신속히 대응할 수 있도록 돕는다.
5. 실제 프로젝트(Spring Boot)에서 Flyway 도입 및 활용
이제 실제로 Spring Boot 애플리케이션에 Flyway를 도입하여 운영 환경에서의 스키마 변경 관리를 개선해보려고 한다. (update를 validate로 바꾸기 위한 노력이다...) 현재 프로젝트에서는 PostgreSQL을 사용하고 있다. 아래 실습은 이러한 환경에서 Flyway를 도입하고, 스키마 불일치 문제를 해결(Index 추가)하는 과정이다.
5.1. 의존성 추가 및 교체
dependencies {
implementation "org.flywaydb:flyway-core"
}
먼저 의존성을 추가해 줬다. 하지만 실제 실행시킬 때 다음과 같은 에러가 났다.
2025-02-27 03:24:36.010 [main] ERROR 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]: Failed to initialize dependency 'flywayInitializer' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Unsupported Database: PostgreSQL 14.11
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Unsupported Database: PostgreSQL 14.11
Caused by: org.flywaydb.core.api.FlywayException: Unsupported Database: PostgreSQL 14.11
해당 오류는 기본 flyway-core 의존성이 PostgreSQL 14.11을 지원하지 않아 발생한 문제이다. 처음에는 flyway-core만 추가했더니 "Unsupported Database: PostgreSQL 14.11" 에러가 발생했고, 이를 최신 버전으로 시도해도 해결되지 않았다. 결국, PostgreSQL 전용 Flyway 모듈인 flyway-database-postgresql를 추가해서 문제를 해결할 수 있었다. (fly-core는 없어도 실행이 됐다.)
dependencies {
implementation "org.flywaydb:flyway-database-postgresql:11.3.3"
}
https://documentation.red-gate.com/fd/postgresql-database-277579325.html
(공식문서 참고)
5.2. 애플리케이션 설정
application.properties 또는 application.yml 파일에 Flyway 관련 기본 설정을 추가한다. 아래는 application.yml에 내가 설정한 요소들이다.
spring:
datasource:
defer-datasource-initialization: false # Flyway 마이그레이션 이후 데이터소스 초기화를 보장하기 위해 false로 설정한다.
flyway:
enabled: true # Flyway 마이그레이션 기능을 활성화한다.
baseline-on-migrate: true # 기존 스키마가 있을 경우, 초기 베이스라인 버전을 생성하여 이후 마이그레이션을 적용할 수 있도록 한다.
baseline-version: "1" # 베이스라인으로 지정할 버전 번호를 설정한다.
locations: classpath:db/migration # 마이그레이션 스크립트가 위치한 경로를 지정한다.
이 설정은 Flyway가 classpath:db/migration 경로에 있는 마이그레이션 파일들을 기준으로 초기 베이스라인 버전("1")을 생성하고, 이후 새로운 마이그레이션을 순차적으로 적용한다.
나는 기존에 Hibernate의 update 옵션을 사용하여 데이터베이스에 이미 테이블이 존재하는 상태였기 때문에, 해당 상태를 Flyway의 베이스라인 버전으로 지정하고자 했다. 이를 위해 별도의 V1 마이그레이션 파일을 생성하지 않고, baseline-on-migrate: true와 baseline-version: "1" 옵션을 사용하여 현재 스키마 상태를 버전 1로 간주하도록 설정했다.
여기서 baseline 기능은 기존 데이터베이스 스키마를 Flyway 마이그레이션 관리의 시작점으로 등록하는 기능이다. 즉, 이미 운영 중인 데이터베이스에 테이블, 컬럼, 인덱스 등이 존재하는 경우, 해당 스키마 상태를 Flyway가 기준 버전으로 인식하도록 설정하는 것이다.
또한, 이전에 우리 프로젝트에선 defer-datasource-initialization:true로 data.sql 스크립트를 Hibernate 초기화 이전에 실행했다. 하지만 이것은 flyway 함께 사용할 수 없기 때문에 오류가 발생했고, 따라서 false로 변경해 줬다.
5.3. 마이그레이션 파일 작성
baseline 기능을 통해 마이그레이션 파일을 작성하지 않고 애플리케이션을 실행하니, flyway_schema_history 테이블에 데이터 하나가(V1) 생성되었다. 이후 인덱스 최적화를 위해 아래와 같이 별도의 마이그레이션 파일(V2__add_index_courses.sql)을 작성하였다.
만약 프로젝트 초기부터 Flyway를 도입한다면, 초기 스키마 설정부터 Flyway 관리 하에 둘 수 있으므로 V1__Initial_setup.sql 파일을 작성하여 테이블 생성, 인덱스 생성 등 모든 스키마 변경 이력을 관리할 수 있다.
이처럼, 기존 테이블이 이미 있는 경우에는 Flyway의 baseline 기능을 활용하여 현재 스키마를 버전 1로 설정하고, 이후 변경 사항은 별도의 마이그레이션 파일(V2 등..)로 관리한다.
파일 명은 V숫자__description.sql로 각자가 설정해 놓은 위치에 넣어놓으면 된다. (나는 classpath:db/migration로 설정했다.)
5.4. 애플리케이션 실행 및 마이그레이션 적용 확인
애플리케이션이 실행되면 Flyway는 자동으로 flyway_schema_history 테이블을 생성한다. 이 테이블은 Flyway가 적용한 모든 마이그레이션 파일의 버전, 설명, 체크섬, 실행 시간, 상태 등의 정보를 기록하여, 이후 중복 적용되지 않도록 관리하고, 실행 결과를 한눈에 확인할 수 있게 해 준다.
예를 들어, 이미 운영 환경에서 Flyway를 도입하여 baseline 기능을 사용한 경우, 최초 실행 시 flyway_schema_history 테이블에 하나의 레코드가 생성된다. 이 레코드는 현재 스키마 상태를 기준 버전(V1)으로 간주한 결과이며, 이후 추가되는 마이그레이션 파일(V2, V3 등)이 순차적으로 적용되면 해당 테이블에 새로운 버전의 레코드들이 추가된다. 따라서 로그뿐만 아니라 flyway_schema_history 테이블을 직접 조회함으로써 1. 각 마이그레이션 파일이 올바르게 실행되었는지, 2. 버전 정보와 체크섬이 일치하는지, 3. 실행 상태(success 여부 등)가 정상적인지를 확인할 수 있다.
내 경우에는 이미 baseline 기능으로 버전 1이 설정되었기 때문에, 이후에 작성한 V2 마이그레이션 SQL이 정상적으로 실행되어 flyway_schema_history 테이블에 버전 2에 해당하는 레코드가 추가됐는지를 확인해야 한다. 실제로 나는 다음과 같이 추가되었으며, 잘 동작했다.
이와 같이 Flyway의 schema history를 통해 모든 마이그레이션이 올바르게 적용되었음을 파악할 수 있으며, 이를 기반으로 스키마 변경 작업이 데이터베이스에 반영되었는지도 확인할 수 있다.
물론 flyway가 꼭 정답은 아니다. 생각해보면 항상 동일한 사용자로 기록되어 누가 쿼리를 수행했는지 추적하기도 어렵고, 비효율적인 프로세스와 네이밍 규칙을 통한 버전 충돌이 일어날 수 도 있다. 각 단점의 해결방법도 생각해보며 각자 환경에 맞게 사용해야 한다.
결론
Hibernate의 ddl-auto update 옵션은 개발 환경에서는 매우 편리했지만, 실제 운영 환경에서는 인덱스와 같은 자잘한 스키마 변경 사항들이 제대로 반영되지 않아 큰 패착을 당했다. 사실, 처음 개발하여 배포하는 과정이라면 단순히 create-drop으로 다시 애플리케이션을 실행시켜 인덱스 추가를 한 뒤 validate로 변경해도 충분했겠지만, 나는 실제 운영되는 서비스의 DB를 건드려야 했기에 매우 조심스러웠다. 하지만 이러한 상황을 계기로 문제를 하나하나 자세히 파악하고 정리할 수 있었다.
이번을 계기로 flyway를 잘 도입했으니, 다음에는 인덱스 최적화에 관한 내용을 다루는 글로 돌아와야겠다~
'Spring' 카테고리의 다른 글
[Spring/Test] 분산락을 통한 동시성 제어 - Troubleshooting (3) | 2024.12.08 |
---|---|
[Spring] MDC + Logback을 활용한 Discord Webhook 연동하기(2) - Troubleshooting (1) | 2024.12.02 |
[Spring] MDC + Logback을 활용한 Discord Webhook 연동하기(1) (0) | 2024.11.19 |
[Spring] JPA 지연 로딩으로 인한 equals 비교 오류 해결 - Troubleshooting (0) | 2024.10.04 |