728x90
반응형
로컬환경에서 각자 개발을 할 때,
DB 스키마가 변경 된다던지 하는 경우 서로 DB가 달라질 수 있고
에러가 발생할 수 있다.
그래서 변경될때마다 공유해주어야하는 불편함이있었다.
자동으로 할 수 없을까 했는데 Flyway라는 것을 이용하면
협업할 때 유용하게 DB를 자동으로 마이그레이션 해줄 수 있다고 해서
적용해보았다.
🚀 오늘의 주제: Flyway를 이용한 DB 마이그레이션 자동화
1. Flyway란? (간단한 설명)
Flyway는 오픈소스 데이터베이스 버전 관리(Migration) 툴입니다. 형상관리 툴인 Git을 통해 소스코드의 이력을 버전별로 관리하듯이, 데이터베이스 스키마(테이블 구조, 인덱스 등)의 변경 이력을 버전별로 안전하고 체계적으로 관리할 수 있게 도와주는 라이브러리입니다.
💡 왜 도입했을까? (배경)
- 로컬 개발 환경, 테스트 서버, 실운영 서버의 DB 스키마 상태를 항상 완벽하게 동일한 상태로 유지하기 위함입니다.
- MSA 구조에서 여러 서비스가 각자의 독립된 DB를 가질 때, 테이블 생성 및 수정을 수동으로 하던 노가다와 실수(Human Error)를 방지하기 위함입니다.
- spring.jpa.hibernate.ddl-auto=update 설정은 실무 프로덕션 환경에서 데이터가 날아갈 위험이 있어 절대 사용하면 안 되기 때문에, Flyway를 통해 안전하게 SQL 스크립트로 상태를 제어합니다.
2. 프로젝트 적용 과정
우리 프로젝트(멀티 모듈 MSA)에 Flyway를 도입하고 적용한 단계별 과정입니다.
① 의존성 추가 (build.gradle)
공통 모듈 또는 DB 접근이 필요한 각 마이크로서비스 모듈의 build.gradle에 Flyway 라이브러리를 추가했습니다.
- implementation 'org.flywaydb:flyway-core'
- implementation 'org.flywaydb:flyway-mysql'
② 환경 설정 (application.yml)
스프링 부트가 켜질 때 Flyway가 자동으로 동작하도록 설정을 켜고, 기존 Hibernate의 자동 테이블 생성 기능(ddl-auto)은 꺼두었습니다.
- spring.jpa.hibernate.ddl-auto: validate (Flyway가 만든 스키마와 Entity가 일치하는지 검증만 수행)
- spring.flyway.enabled: true
- spring.flyway.baseline-on-migrate: true (기존에 데이터가 있는 DB일 경우 초기화 기준점 설정)
③ 마이그레이션 SQL 스크립트 작성 (규칙 준수)
Flyway는 정해진 디렉토리 구조와 네이밍 규칙을 엄격하게 따라야 인식합니다.
- 기본 경로: src/main/resources/db/migration/
- 파일명 규칙: V[버전]__[설명].sql (⚠️ 버전 뒤에 언더바 '_'를 반드시 2개 붙여야 함)
실제 작성한 첫 번째 스크립트 예시 (V1__init.sql):
-- PostGIS 확장 활성화 (public 스키마에 설치 권장)
CREATE EXTENSION IF NOT EXISTS postgis SCHEMA public;
-- company 스키마 생성 및 검색 경로 설정
CREATE SCHEMA IF NOT EXISTS company;
-- geometry 타입을 찾기 위해 public을 검색 경로에 포함해야 함
SET search_path TO company, public;
-- p_product_categories (상품 분류)
CREATE TABLE p_product_categories (
category_id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL,
depth INTEGER DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by VARCHAR(36),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by VARCHAR(36),
deleted_at TIMESTAMP,
deleted_by VARCHAR(36)
);
-- p_companies (업체)
CREATE TABLE p_companies (
company_id UUID PRIMARY KEY,
company_name VARCHAR(255) NOT NULL,
company_type VARCHAR(30) NOT NULL, -- PRODUCER / RECEIVER
phone VARCHAR(20),
description TEXT,
business_number VARCHAR(20) NOT NULL,
hub_id UUID NOT NULL,
latitude GEOMETRY(Point, 4326) NOT NULL,
longitude GEOMETRY(Point, 4326) NOT NULL,
address TEXT,
logo_url VARCHAR(500),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by VARCHAR(36),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by VARCHAR(36),
deleted_at TIMESTAMP,
deleted_by VARCHAR(36)
);
-- p_products (상품)
CREATE TABLE p_products (
product_id UUID PRIMARY KEY,
company_id UUID NOT NULL REFERENCES p_companies(company_id),
category_id UUID REFERENCES p_product_categories(category_id),
name VARCHAR(500) NOT NULL,
price NUMERIC(12,2) NOT NULL,
description TEXT,
thumbnail_url VARCHAR(500),
status VARCHAR(30) DEFAULT 'ON_SALE', -- ON_SALE / SOLD_OUT
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by VARCHAR(36),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by VARCHAR(36),
deleted_at TIMESTAMP,
deleted_by VARCHAR(36)
);
-- p_product_options (상품 옵션 — SKU 단위)
CREATE TABLE p_product_options (
product_option_id UUID PRIMARY KEY,
product_id UUID NOT NULL REFERENCES p_products(product_id),
options_name VARCHAR(255),
extra_price NUMERIC(12,2) DEFAULT 0,
status VARCHAR(30) DEFAULT 'ON_SALE',
display_order INTEGER,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by VARCHAR(36),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by VARCHAR(36),
deleted_at TIMESTAMP,
deleted_by VARCHAR(36)
);
-- p_delivery_addresses (배송지 관리)
CREATE TABLE p_delivery_addresses (
address_id VARCHAR(36) PRIMARY KEY,
company_id UUID NOT NULL REFERENCES p_companies(company_id),
address_name VARCHAR(100) NOT NULL,
recipient_name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
address TEXT NOT NULL,
address_detail VARCHAR(255),
postal_code VARCHAR(10),
is_default BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_by VARCHAR(36),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_by VARCHAR(36),
deleted_at TIMESTAMP,
deleted_by VARCHAR(36)
);
-- 인덱스 추가
CREATE INDEX idx_p_companies_hub_id ON p_companies(hub_id);
CREATE INDEX idx_p_products_company_id_status ON p_products(company_id, status);
CREATE INDEX idx_p_companies_deleted_at ON p_companies(deleted_at);
CREATE INDEX idx_p_products_deleted_at ON p_products(deleted_at);
CREATE INDEX idx_p_product_options_deleted_at ON p_product_options(deleted_at);
CREATE INDEX idx_p_delivery_addresses_company_id ON p_delivery_addresses(company_id);
반응형
'Study' 카테고리의 다른 글
| [내일배움캠프 TIL] 30일차 - Arch unit (0) | 2026.05.18 |
|---|---|
| [내일배움캠프 TIL] 29일차 - 테라폼 (Terraform)에 대해 (0) | 2026.05.15 |
| [내일배움캠프 TIL] 28일차 - CQRS (1) | 2026.05.14 |
| [내일배움캠프 TIL] 27일차 - Saga pattern(사가 패턴) (0) | 2026.05.13 |
| [내일배움캠프 TIL] 26일차 - DB 성능 최적화 전략 (0) | 2026.05.12 |