Zum Hauptinhalt springen

마이그레이션 가이드

Flyway 구성

Lumie는 Flyway를 사용하여 데이터베이스 스키마를 관리합니다. V18(RLS baseline) 이후 마이그레이션은 단일 경로로 통합되었습니다.

src/main/resources/db/migration/
└── public/ # 단일 경로 — public 스키마 전체
├── V1__create_platform_tables.sql
├── V2__create_users_table.sql
├── ...
├── V52__attendance_session_unique_class_date.sql
└── V53__attendance_record_default_pending.sql

tenant/ 경로는 V18 RLS 마이그레이션에서 제거되었습니다. Schema-per-tenant 전략이 폐기된 이후 모든 테이블이 public 스키마로 통합되었습니다.

마이그레이션 실행 방식

애플리케이션 시작 시 Flyway가 public 스키마에 대해 순차적으로 실행합니다.

spring.flyway.locations=classpath:db/migration/public
spring.flyway.schemas=public
spring.flyway.user=<postgres 역할> # 마이그레이션 전용 역할 (SUPERUSER)
spring.datasource.username=lumie_app # 런타임 역할 (NOBYPASSRLS)

역할 분리:

  • postgres (SUPERUSER): Flyway 마이그레이션 실행 전용. RLS를 우회하여 전체 행에 접근 가능
  • lumie_app (NOSUPERUSER NOBYPASSRLS): 애플리케이션 런타임 연결. RLS 정책이 항상 적용됨

마이그레이션 작성 규칙

파일 네이밍

V{버전}__{설명}.sql
  • V 접두사 필수
  • 버전은 정수 (1, 2, 3, ...)
  • 밑줄 2개(__)로 버전과 설명 구분
  • 설명은 snake_case

DO

  • 하나의 마이그레이션 파일은 하나의 논리적 변경만 포함
  • IF NOT EXISTS를 사용하여 멱등성 확보
  • 큰 테이블 변경은 별도 마이그레이션으로 분리
  • 인덱스 생성은 CONCURRENTLY 사용 (다운타임 방지)
  • 새 테넌트 스코프 테이블은 반드시 RLS 패턴 적용

DON'T

  • DDL과 DML을 같은 파일에 섞지 않기
  • 이미 실행된 마이그레이션 파일 수정하지 않기 — 체크섬 불일치로 부트 실패
  • DROP TABLE / DROP COLUMN을 함부로 사용하지 않기 (데이터 손실)
  • ALTER TYPE으로 enum 변경하지 않기 (별도 전략 필요)

신규 테넌트 스코프 테이블 패턴

-- V53__create_example_table.sql

CREATE TABLE IF NOT EXISTS example_table (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
version BIGINT NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_example_table_tenant_id
ON example_table (tenant_id);

-- RLS 적용 (필수)
ALTER TABLE example_table ENABLE ROW LEVEL SECURITY;
ALTER TABLE example_table FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON example_table
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint)
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint);

-- lumie_app 권한 부여 (필수)
GRANT SELECT, INSERT, UPDATE, DELETE ON example_table TO lumie_app;
GRANT USAGE, SELECT ON SEQUENCE example_table_id_seq TO lumie_app;

컬럼 추가 예시

-- V54__add_example_column.sql

ALTER TABLE students
ADD COLUMN IF NOT EXISTS grade_level VARCHAR(10);

플랫폼 vs 테넌트 테이블 배치 기준

기준플랫폼 테이블 (RLS 없음)테넌트 테이블 (RLS 있음)
전역 디렉토리 (tenants, owner_directory)O
빌링 계약 (plans, subscriptions, invoices)O
학원별 업무 데이터O
테넌트 간 공유 없는 데이터O

플랫폼 테이블은 lumie_apptenant_id 필터 없이 전체 행을 읽을 수 있습니다. 접근 제어는 앱 계층(Spring Security)이 담당합니다.

트러블슈팅

마이그레이션 실패 시

# 실패 이력 조회
SELECT * FROM flyway_schema_history WHERE success = false;

# Flyway repair (실패 기록 정리)
./gradlew flywayRepair

# 또는 직접 삭제
DELETE FROM public.flyway_schema_history WHERE success = false;

체크섬 불일치

이미 적용된 마이그레이션 파일을 수정하면 부트 실패합니다. 절대 수정하지 마세요. 변경이 필요하면 새 버전 파일을 만드세요.

RLS로 인해 행이 안 보일 때

-- 현재 세션의 tenant_id 설정 확인
SELECT current_setting('app.tenant_id', true);

-- postgres 역할로 접속 시 전체 행 조회 가능 (SUPERUSER)
-- lumie_app 역할로 접속 시 SET LOCAL app.tenant_id = '<id>' 필요