본문으로 건너뛰기

마이그레이션 가이드

목적

모든 백엔드 스키마 변경은 lumie-backend/app/src/main/resources/db/migration/public/ 아래의 Flyway migration을 통해 이뤄집니다. 이 페이지는 tenant 격리, 소유권 경계, 배포 순서를 깨뜨리지 않고 migration을 추가하거나 검토하는 how-to 가이드입니다.

전제 조건

  • lumie-backend/gradlew와 migration 디렉터리 lumie-backend/app/src/main/resources/db/migration/public/를 포함한 workspace checkout
  • 대상 테이블 계열에 대한 선택: 플랫폼 소유 또는 tenant 범위
  • :app:flywayInfo를 실행할 계획이라면, 백엔드의 데이터베이스 설정이 로컬 환경 또는 dev 설정에 이미 준비되어 있어야 함

이름과 위치

규칙요구사항
위치lumie-backend/app/src/main/resources/db/migration/public/
이름V<number>__short_description.sql
순서다음 미사용 정수 버전
범위하나의 일관된 스키마 변경 또는 데이터 수리
롤백안전하게 되돌릴 수 없을 때는 그 이유를 설명

1단계: 다음 버전 번호 선택

cd /path/to/Lumie/lumie-backend
rg --files app/src/main/resources/db/migration/public | sort -V | tail -n 10

예상 성공 신호: 마지막 몇 개 migration 파일명이 숫자 순으로 보여서 다음 미사용 V<number>__...sql을 선택할 수 있습니다.

2단계: 백엔드 리포지토리에서 마이그레이션 초안 작성

새 파일을 다음 위치에 만듭니다.

app/src/main/resources/db/migration/public/V<number>__short_description.sql

하나의 migration에는 하나의 일관된 스키마 변경만 담으세요. 관련 없는 영역을 가로지른다면 리뷰 전에 분리해야 합니다.

3단계: 올바른 소유권 패턴 적용

새 tenant 소유 테이블에는 다음 체크리스트를 사용합니다.

tenant_id bigint not null
alter table <table> enable row level security;
alter table <table> force row level security;
create policy tenant_isolation on <table>
using (tenant_id = nullif(current_setting('app.tenant_id', true), '')::bigint)
with check (tenant_id = nullif(current_setting('app.tenant_id', true), '')::bigint);

또한 접근 패턴에 맞는 인덱스를 추가해야 합니다. 대부분의 tenant 범위 조회 인덱스는 tenant_id로 시작해야 합니다.

예상 성공 신호: 모든 새 tenant 테이블에 tenant_id가 있고, RLS가 활성화 및 강제되며, policy가 current_setting('app.tenant_id')를 기준으로 동작합니다.

플랫폼 체크리스트는 테이블이 의도적으로 Lumie 소유의 cross-tenant 데이터일 때만 사용하세요.

플랫폼 테이블은 의도적으로 Lumie 소유 cross-tenant 데이터일 때만 RLS를 생략할 수 있습니다. migration 주석은 이를 명시해야 합니다.

예시:

  • tenants
  • plans
  • subscriptions
  • billing_keys
  • payment_transactions
  • event_publication
  • shedlock

예상 성공 신호: migration 주석이나 주변 DDL만 보아도 접근 제어가 RLS가 아니라 애플리케이션 계층 권한 부여에 있음을 알 수 있습니다.

4단계: 안전한 변경 패턴 선호

변경선호 패턴
nullable 컬럼 추가컬럼 추가 후 코드 배포, 필요 시 나중에 backfill
non-null 컬럼 추가nullable/default로 추가, backfill, 이후 not null 강제
큰 테이블에 인덱스 추가migration 도구가 지원하면 concurrent index 사용
tenant-safe FK 추가참조 대상 테이블에 (id, tenant_id) unique index 포함
RLS 수리tenant_id backfill, non-null 강제, 그 후 RLS 활성화/강제
개념 이름 변경모든 호출자가 이동할 때까지 코드와 문서에서 호환성 유지

5단계: 변경 사항 검토

cd /path/to/Lumie/lumie-backend
git diff -- app/src/main/resources/db/migration/public

예상 성공 신호: diff에 의도한 migration 파일 또는 같은 스키마 변경에 속한 후속 수정만 보입니다.

마무리 전에 다음 체크리스트를 사용하세요.

  1. 이 테이블은 플랫폼 범위인가, tenant 범위인가?
  2. tenant 범위라면 tenant_id, RLS, tenant 인지 인덱스가 있는가?
  3. 애플리케이션 코드가 이 테이블을 건드리기 전에 tenant 컨텍스트를 설정하는가?
  4. 모듈 경계를 넘는 hard FK는 의도적으로 선택된 것인가?
  5. migration이 라이브 데이터에 대한 파괴적 재작성을 피하는가?
  6. 관련 제품 문서를 업데이트해야 하는가?

검증

cd /path/to/Lumie/lumie-backend
rg -n "tenant_id|ENABLE ROW LEVEL SECURITY|FORCE ROW LEVEL SECURITY|tenant_isolation" \
app/src/main/resources/db/migration/public

예상 성공 신호: tenant 범위 migration이 일관되게 tenant 컬럼, RLS 구문, policy 정의를 보여줍니다.

cd /path/to/Lumie/lumie-backend
./gradlew :app:flywayInfo

예상 성공 신호: Flyway가 새 버전을 순서대로 한 번만 나열합니다. 로컬 데이터베이스 상태에 따라 적용 전에는 pending, 적용 후에는 successful로 나타나야 합니다.

자주 발생하는 실패

  • 중복 migration 버전 번호는 Flyway 순서 충돌을 일으킵니다.
  • tenant 소유 테이블에 FORCE ROW LEVEL SECURITY가 없어 런타임 role의 우회 경로가 남습니다.
  • 주요 접근 경로가 tenant 범위인데도 새 인덱스에 tenant_id가 빠집니다.
  • 코드가 의도적으로 soft reference를 유지하는 모듈 경계에 hard FK를 추가합니다.
  • 백엔드 데이터베이스 환경이 아직 로컬에 준비되지 않아 :app:flywayInfo가 실패합니다.

관련 페이지