Files
project-infra/docs/standards/infra/flyway.md
T

13 KiB

Flyway 기준

목적

이 문서는 PostgreSQL 기반 서비스에서 Flyway를

  • 어디서 실행할지 (Kubernetes Job)
  • 어떤 순서로 실행할지 (validate / info / migrate / app rollout)
  • 어떤 배포 흐름과 맞물릴지 (Helm hook / Argo CD sync-wave)
  • config를 어떻게 공급할지 (env var + Secret via Vault Secrets Operator)
  • schema history table을 어떻게 둘지
  • baseline / repair / out-of-order / undo를 어떻게 다룰지
  • non-transactional DDL을 어떻게 처리할지 를 먼저 고정한다.

이 문서의 목표는 다음과 같다.

  • Flyway를 앱 startup 내부 로직처럼 숨기지 않는다
  • validate / migrate / repair / baseline의 역할을 분리한다
  • schema history table을 운영 감사 추적의 일부로 본다
  • migration Job을 1000+ 서비스 규모에서 재현 가능하게 표준화한다
  • DB 변경을 애플리케이션 rollout과 분리해 운영한다

공식 의미

  • Flyway validate는 적용된 migration과 로컬 migration 사이의 이름/타입/checksum 차이, 로컬에 없는 적용 버전, 아직 적용되지 않은 로컬 버전을 검증한다.
  • migrate는 schema history table이 없으면 자동 생성하고 최신 migration까지 적용한다.
  • schema history table은 migration 실행 내역, checksum, 성공/실패 상태를 기록하는 audit trail이다.
  • repair는 schema history table을 수정하는 명령이며, 실패한 migration 엔트리 제거, checksum/description/type 재정렬, missing migration 삭제 표시를 수행한다. user object는 정리하지 않는다.
  • schema history table 기본 이름은 flyway_schema_history다.
  • schema history table 위치는 table, defaultSchema, schemas로 제어할 수 있다.
  • createSchemas=false일 때 history table이 들어갈 schema가 미리 준비되지 않으면 migrate가 실패할 수 있다.
  • 기존 non-empty schema에 Flyway를 도입할 때 history table이 없으면 baseline 또는 baselineOnMigrate가 필요할 수 있다.
  • schema history에는 Pending, Success, Missing, Out of Order, Outdated, Superseded, Deleted 등 상태가 기록될 수 있다.
  • Flyway는 migration 실행 중 schema history table에 advisory lock을 걸어 동시 실행을 직렬화한다. 다중 replica Job 수준의 race를 방지한다.
  • cleanDisabled는 Flyway 9 이후 기본 true. production에서는 반드시 true를 명시한다.
  • Flyway 8.2+ 에서 -- flyway:executeInTransaction=false directive로 migration 파일 단위 트랜잭션 비활성화가 가능하다.
  • Flyway Community(OSS)는 undo(U__) migration을 지원하지 않는다. Undo는 Teams/Enterprise 상용 기능이다.
  • 환경변수 config 지원: FLYWAY_URL, FLYWAY_USER, FLYWAY_PASSWORD, FLYWAY_LOCATIONS, FLYWAY_SCHEMAS, FLYWAY_DEFAULT_SCHEMA, FLYWAY_TABLE, FLYWAY_BASELINE_ON_MIGRATE, FLYWAY_VALIDATE_ON_MIGRATE, FLYWAY_CLEAN_DISABLED, FLYWAY_OUT_OF_ORDER, 그 외 FLYWAY_*.

기본 규칙

1. Flyway는 앱 startup이 아니라 독립 실행 단계

운영 환경에서 Flyway는 다음 중 하나로만 실행한다.

  • Kubernetes Job (권장)
  • CI/CD 명시 단계
  • 운영자 명시 실행 절차

기본 금지:

  • 애플리케이션 startup 시 자동 migration
  • Spring Boot spring.flyway.enabled=true로 앱 부팅 경로에 포함
  • readiness/liveness와 migration 실패를 섞는 구조

2. 기본 순서는 info → validate → migrate → info → app rollout

운영 기본 순서:

  1. flyway info (pending 확인)
  2. flyway validate
  3. flyway migrate
  4. flyway info (결과 확인)
  5. 애플리케이션 rollout

validateOnMigrate=true가 기본값이지만, 운영 절차상 validate를 분리 initContainer 또는 사전 단계로 둔다.

3. 배포 흐름 안에서 app보다 먼저 실행 — 두 가지 패턴

패턴 A: Helm hook

annotations:
  "helm.sh/hook": "pre-upgrade,pre-install"
  "helm.sh/hook-weight": "-10"
  "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"

패턴 B: Argo CD sync-wave

annotations:
  argocd.argoproj.io/sync-wave: "-1"
  argocd.argoproj.io/hook: Sync
  argocd.argoproj.io/hook-delete-policy: BeforeHookCreation

기본:

  • 두 패턴을 혼용하지 않는다 (Argo CD가 Helm chart를 렌더링할 때 Helm hook을 일반 리소스로 취급해 순서가 꼬임)
  • 배포 도구에 맞춰 한쪽만 사용

4. migration Job 안전 설정 체크리스트

Job manifest에 반드시 다음이 있어야 한다.

  • parallelism: 1, completions: 1
  • backoffLimit: 0 또는 작은 값 (1~2)
  • activeDeadlineSeconds (권장 1800 = 30분, 대형 migration은 더 길게)
  • ttlSecondsAfterFinished (권장 86400 = 1일)
  • restartPolicy: Never
  • 이미지 digest pinning (flyway/flyway@sha256:...)
  • imagePullPolicy: IfNotPresent
  • resources.requests/limits
  • securityContext: runAsNonRoot: true, readOnlyRootFilesystem: true, capabilities: drop: [ALL]
  • Pod-level seccompProfile: RuntimeDefault
  • fsGroup 명시 (필요 시)

5. config는 환경변수 + Secret

Flyway CLI는 FLYWAY_* 환경변수를 읽는다. Secret은 Vault Secrets Operator(VSO) 또는 External Secrets Operator를 통해 클러스터에 동기화된 Secret에서 주입한다.

필수 env var:

  • FLYWAY_URLjdbc:postgresql://host:5432/db
  • FLYWAY_USER
  • FLYWAY_PASSWORD — Secret에서 주입
  • FLYWAY_LOCATIONSfilesystem:/flyway/sql

운영 권장 env var:

  • FLYWAY_SCHEMAS — 대상 schema
  • FLYWAY_DEFAULT_SCHEMA — history table 위치
  • FLYWAY_TABLE — 기본 flyway_schema_history
  • FLYWAY_VALIDATE_ON_MIGRATE=true
  • FLYWAY_BASELINE_ON_MIGRATE=false (운영 기본값)
  • FLYWAY_CLEAN_DISABLED=true (production 필수)
  • FLYWAY_OUT_OF_ORDER=false
  • FLYWAY_MIXED=false

6. cleanDisabled=true는 production 필수

flyway clean은 모든 object를 drop 하는 파괴적 명령이다.

  • production: FLYWAY_CLEAN_DISABLED=true 반드시 명시 (Flyway 9+ 기본값이지만 명시적으로 선언)
  • dev/test: 필요 시 false 허용, 단 접근 권한 분리

7. migration SQL은 ConfigMap 또는 이미지 레이어로

옵션:

  • ConfigMap: 서비스 manifest와 함께 Argo CD로 관리. small/medium migration set에 적합. ConfigMap 1MiB 제한 주의.
  • 이미지 레이어: 서비스 repo에서 migration SQL을 Docker image로 빌드하고 Flyway image와 합쳐 사용. 대규모 migration set에 적합.

기본:

  • 두 방식 모두 Git이 source of truth
  • 운영 서버에서 kubectl edit configmap으로 migration 편집 금지

8. schema history table은 운영 감사 추적의 일부

수동 UPDATE / DELETE 금지. 위치는 명시적으로 결정.

기본:

  • service별 schema를 FLYWAY_DEFAULT_SCHEMA로 지정 (예: auth_server)
  • history table 이름은 기본값 flyway_schema_history 유지
  • 여러 서비스의 history table을 하나의 schema에 몰지 않음

9. createSchemas=false면 history schema를 사전 준비

createSchemas=false를 쓰면 history table이 들어갈 schema를 별도 준비해야 한다.

기본:

  • FLYWAY_INIT_SQLCREATE SCHEMA IF NOT EXISTS 지시 가능
  • 또는 CNPG Cluster.bootstrap.initdb.postInitSQL에서 schema 사전 생성
  • 생성 책임이 누구인지 문서화

10. baseline은 예외 절차

허용 예:

  • legacy DB를 처음 Flyway 관리로 편입
  • 기존 non-empty schema를 Flyway에 편입할 때

기본 금지:

  • 새 프로젝트인데 baseline부터 쓰기
  • 운영 배포 파이프라인에서 습관적으로 baseline 사용

11. baselineOnMigrate는 기본값 아님

baselineOnMigrate=true는 도입/전환 시 편의를 줄 수 있지만, 운영 기본값으로 두지 않는다.

이유:

  • 예상치 못한 기존 schema를 "정상 상태"처럼 받아들일 수 있다
  • 실수 탐지력이 떨어진다

FLYWAY_BASELINE_ON_MIGRATE=false로 명시.

12. repair는 예외 절차

허용 예:

  • 의도적으로 migration 파일을 수정했고 checksum 정렬이 필요
  • missing migration을 문서화된 절차로 정리
  • failed repeatable migration 이후 history 정리

기본 금지:

  • validate 오류가 나면 원인 분석 없이 바로 repair
  • CI/CD에서 습관적으로 repair 실행

13. repair는 user object를 고쳐주지 않는다

repair는 schema history table만 정리한다. 실패한 migration이 남긴 DB object 정리, 불완전한 DDL/DML 정리는 별도 절차로 수행해야 한다.

14. 적용된 migration 파일은 수정 금지

이유:

  • checksum mismatch
  • 재현 불가
  • 환경 간 drift

대응:

  • 새 migration으로 교정
  • 정말 예외적인 수정만 공식 repair 절차와 함께 수행

15. out-of-order는 기본 금지

Out-of-order migration은 전체 migration history를 다시 실행할 때 다른 결과를 만들 수 있다.

기본:

  • FLYWAY_OUT_OF_ORDER=false
  • 뒤늦게 빠진 migration을 넣는 방식을 기본값으로 두지 않음
  • 예외 허용 시 영향 범위 검토 문서 필수

16. repeatable migration(R__)은 목적 제한

Repeatable migration은 다음 용도에 제한한다.

  • view 정의
  • function / procedure
  • trigger 재생성
  • reference / static data refresh

기본 금지:

  • 순서가 중요한 핵심 schema change를 repeatable로 남발
  • versioned migration 대신 repeatable로 대체

17. Undo(U__) migration은 만들지 않는다

Flyway Community(OSS)는 undo를 지원하지 않는다.

  • U__ 파일을 repo에 두지 않음 (오해 유발)
  • rollback은 forward-only 새 migration + PITR로 대응

18. locations는 environment별로 흔들지 않는다

migraterepair는 같은 locations 전제를 가져야 한다.

기본:

  • env마다 location 구조가 달라지지 않게 유지
  • 운영과 개발에서 전혀 다른 migration set을 쓰지 않음
  • env별 변수는 placeholders(FLYWAY_PLACEHOLDERS_*)로 분리

19. migration은 서비스 소유권 단위로 분리

기본:

  • auth-server는 auth-server migration set
  • keycloak은 keycloak 고유 migration (사실 Keycloak은 내부 migration을 사용하므로 Flyway 대상이 아님)
  • 공용 migration 프로젝트 금지

20. migration naming / versioning

기본:

  • versioned: V<N>__<snake_case>.sql, N은 증가하는 정수 또는 점표기(예: V12__, V1.2.3__)
  • repeatable: R__<snake_case>.sql
  • 이름은 변경 의도를 드러나게 작성

예:

  • V42__add_refresh_token_audit_columns.sql
  • R__refresh_user_views.sql

21. destructive change는 expand → migrate → contract

db-and-migration.md #19 참조. Flyway 입장에서 각 단계는 별도 릴리즈의 versioned migration으로 나간다.

22. non-transactional DDL은 executeInTransaction=false

Postgres에서 트랜잭션 밖 실행이 필요한 DDL:

  • CREATE INDEX CONCURRENTLY
  • REINDEX CONCURRENTLY
  • ALTER TYPE ... ADD VALUE
  • VACUUM

migration 파일 상단:

-- flyway:executeInTransaction=false
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

기본:

  • 이런 DDL은 전용 migration 파일로 분리 (다른 statement와 섞지 않음)
  • runtime 추정치 주석
  • low-traffic window로 배포 일정 조정

23. rollback은 Flyway 명령에 기대지 않는다

운영 기본 rollback:

  • 새 migration으로 수정
  • PostgreSQL PITR (CNPG bootstrap.recovery)
  • 애플리케이션 버전 rollback + DB 호환 윈도우 유지 (expand-contract의 효과)

rollout undo가 DB schema rollback을 대신하지 않는다.

24. 현재 스택 기준 기본 권장안

  • auth-server
    • Flyway Job (Helm hook 또는 Argo sync-wave)
    • FLYWAY_DEFAULT_SCHEMA=auth_server
    • validate → migrate → app rollout
    • digest pinning
  • keycloak
    • Keycloak 자체 migration 사용, Flyway 대상 아님
  • test-server
    • DB가 없으면 Flyway 대상 아님
  • 운영 절차
    • repair/baseline은 예외 승인 절차
    • applied migration 수정 금지
    • CLEAN_DISABLED=true 필수

프로젝트 기준 요약

  • Flyway는 독립 실행 단계 (Kubernetes Job)
  • info → validate → migrate → info → app rollout
  • 배포 흐름 내 순서는 Helm hook 또는 Argo CD sync-wave 중 하나로 통일
  • Job: parallelism: 1, backoffLimit: 0, ttlSecondsAfterFinished, digest pinning, restricted PSA
  • config는 FLYWAY_* env var + Secret (VSO / ESO)
  • FLYWAY_CLEAN_DISABLED=true 필수, FLYWAY_BASELINE_ON_MIGRATE=false, FLYWAY_OUT_OF_ORDER=false
  • schema history table 위치를 FLYWAY_DEFAULT_SCHEMA로 명시
  • baseline / repair / out-of-order는 예외 절차
  • applied migration 수정 금지
  • Undo(U__) 파일 만들지 않음 (OSS 미지원)
  • non-transactional DDL은 -- flyway:executeInTransaction=false로 파일 단위 분리
  • service별 migration ownership 분리
  • destructive migration은 expand → migrate → contract