# db / migration 기준 ## 목적 이 문서는 Kubernetes 상의 PostgreSQL과 Flyway를 기준으로 - 데이터베이스를 어떻게 나눌지 - 어떤 operator / 도구로 운영할지 (CNPG, Zalando, Crunchy, self-managed StatefulSet) - migration을 어디서 어떻게 실행할지 - migration과 배포(Helm / Argo CD)의 순서를 어떻게 보장할지 - validate / migrate / rollback / backup / PITR을 어떤 순서로 볼지 - zero-downtime을 위한 expand-migrate-contract를 어떻게 적용할지 를 먼저 고정한다. 이 문서의 목표는 다음과 같다. - app rollout과 schema 변경을 분리한다 - Keycloak DB와 auth-server DB 경계를 먼저 고정한다 - Flyway를 앱 시작 로직에 숨기지 않는다 - PostgreSQL backup/restore 전략과 migration 전략을 함께 본다 - 1000+ 서비스 규모에서 일관된 migration Job 표준을 만든다 ## 공식 의미 - `pg_dump`는 logical export다. 정기 production 전체 백업 기본값으로는 보통 적합하지 않다. - `pg_basebackup`은 실행 중인 PostgreSQL cluster의 base backup을 만들며 PITR/standby 시작점으로 쓴다. - PostgreSQL PITR은 base backup + WAL archiving 결합이다. - 운영 표준 물리 백업 도구: **pgBackRest**, **WAL-G**. 또는 operator-native (CNPG Barman Cloud, Crunchy PGO). - PostgreSQL의 **대부분 DDL은 트랜잭션 내에서 실행 가능**하지만, `CREATE INDEX CONCURRENTLY`, `REINDEX CONCURRENTLY`, `ALTER TYPE ... ADD VALUE`, `VACUUM`은 트랜잭션 밖에서만 실행된다. - CloudNativePG operator는 CNCF Sandbox 프로젝트로 K8s-native Postgres 운영 표준 후보다. - Flyway `validate`는 적용된 migration과 로컬 migration 사이의 이름/타입/checksum 차이, 로컬에 없는 적용 버전, 아직 적용되지 않은 로컬 버전을 검증한다. - Flyway `validateOnMigrate` 기본값은 `true`, `cleanDisabled` 기본값은 `true` (Flyway 9+). - Flyway `migrate`는 schema history table을 자동 생성하고 최신 migration까지 적용한다. - Flyway Community(OSS)는 **undo(U__) migration을 지원하지 않는다.** Undo는 Teams/Enterprise 전용이다. - Flyway는 migration 실행 시 schema history table에 advisory lock을 걸어 동시 실행을 방지한다. ## RPO / RTO 모든 DB는 다음을 runbook에 먼저 적는다. - RPO / RTO / Retention - 복구 목표 (cluster restore / PITR / standby seed) - 운영 tier (gold / silver / bronze) 이 값들이 없으면 backup 도구 선택이 되지 않는다. `backup-restore.md` 참조. ## 기본 규칙 ### 1. DB 경계는 애플리케이션 경계보다 먼저 고정 다음을 명시적으로 정한다. - Keycloak DB와 auth-server DB를 **물리적 cluster**로 분리할지, 같은 cluster 내 **logical DB / schema**로 분리할지 - test-server가 DB를 가지는지 - migration 소유권이 누구에게 있는지 (보통 서비스 팀) 기본: - 인증 critical data (Keycloak)와 앱 data (auth-server)는 **cluster 분리 권장** - 같은 cluster를 쓰더라도 database / role / schema ownership을 섞지 않음 - 한 migration tool/job이 여러 서비스 schema를 동시에 소유하지 않음 ### 2. K8s 위 Postgres 운영 기본은 operator 1000+ 서비스 규모에서 self-managed StatefulSet은 운영 부담이 너무 크다. Operator를 기본 후보로 둔다. 우선순위 (2026 기준): 1. **CloudNativePG (CNPG)** — CNCF Sandbox, K8s-native, Barman Cloud 내장, `Cluster` / `Backup` / `ScheduledBackup` CRD 2. **Crunchy PGO** — 상용 지원, pgBackRest 내장 3. **Zalando postgres-operator** — Spilo 기반, 레거시 환경 operator를 쓰면 자동으로 얻는 것: - primary/standby 구성 + failover - rolling minor upgrade - WAL archiving + continuous backup - pg_basebackup, PITR, replica re-clone - PodMonitor 연동 ### 3. migration은 앱 startup에 숨기지 않는다 Flyway migration은 **독립 실행 단계**다. 기본: - `validate` → (필요시 `info`) → `migrate` → app rollout 기본 금지: - app container 시작 시 자동 migration (Spring Boot `spring.flyway.enabled=true` + `@SpringBootApplication` 부팅 시 migrate) - readiness/liveness와 migration 실패를 섞는 구조 - "서버가 뜨면 알아서 schema를 맞춘다" 방식 ### 4. Flyway 실행 기본값은 Kubernetes Job 운영 환경에서 Flyway는 다음 중 하나로만 실행한다. - Kubernetes Job (권장) - CI/CD 명시 단계 - 운영자 명시 실행 절차 장기 실행 Deployment에 넣지 않는다. Flyway 예시는 `docs/examples/infra/flyway.md` 참조. ### 5. migration Job은 배포 흐름 안에서 app보다 먼저 실행 migration을 app보다 **선행**시키는 것은 manifest 메타데이터로 선언한다. 패턴 A — Helm hook: ```yaml metadata: 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 + hook: ```yaml metadata: annotations: argocd.argoproj.io/sync-wave: "-1" argocd.argoproj.io/hook: Sync argocd.argoproj.io/hook-delete-policy: BeforeHookCreation ``` 기본: - migration은 sync wave가 app보다 **작은** 값 (먼저 실행) - app Deployment는 wave `0` 또는 그 이상 - Helm과 Argo CD를 혼용하는 경우 **한 쪽으로 통일** (둘 다 hook을 걸면 순서가 꼬인다) ### 6. migration Job 안전 설정 모든 migration Job은 다음을 명시한다. - `parallelism: 1` — 병렬 실행 금지 (Flyway advisory lock이 막아주지만, Job 수준에서도 명시) - `completions: 1` - `backoffLimit: 0` 또는 작은 값 (1~2) — 실패 시 무한 재시도 금지 - `activeDeadlineSeconds` — 타임아웃 (예: 1800) - `ttlSecondsAfterFinished` — 완료 후 자동 정리 (예: 86400) - `restartPolicy: Never` - 이미지는 **digest pinning** (`flyway/flyway@sha256:...`) - `resources.requests/limits` 명시 - `securityContext` restricted PSA 준수 ### 7. validate를 먼저, migrate를 나중에 운영 절차 기본 순서: 1. `flyway info` (pending migration 확인) 2. `flyway validate` 3. `flyway migrate` 4. `flyway info` (결과 확인) 5. app rollout `validateOnMigrate=true` 기본값이 있더라도, 운영 runbook에서는 validate 단계를 **분리 Job** 또는 **initContainer**로 분리한다. `docs/examples/infra/flyway.md` 참조. ### 8. migration source는 Git이 source of truth 중요한 것은 아래다. - versioned migration script (`V__`) - repeatable migration script (`R__`) - migration ordering - schema history table 상태 기본 금지: - 운영 서버에서 migration 파일 수동 수정 - 적용된 migration 파일을 사후 편집 (checksum mismatch) - Flyway schema history table을 사람이 직접 UPDATE/DELETE ### 9. Flyway undo(U__)는 쓰지 않는다 Flyway Community(OSS)는 **U__ 파일을 지원하지 않는다**. Teams/Enterprise에서만 `undo` 명령이 동작한다. 기본: - undo migration 파일을 만들지 않음 - rollback은 forward-only migration + PITR로 수행 - 운영 기본은 "다음 migration으로 앞으로 수정" ### 10. DB backup 전략과 migration 전략을 같이 본다 schema 변경이 production에 들어간다면, 같은 변경 계획 안에 아래가 같이 있어야 한다. - rollback 가능 여부 - 변경 직전 backup 시점 (예: on-demand CNPG `Backup` 실행) - restore 단위 (전체 cluster / logical DB) - PITR 필요 여부 + targetTime 후보 - migration 실패 시 중단 지점 (어느 V__에서 멈췄는지) ### 11. PostgreSQL 운영 기본 백업은 continuous physical backup 운영 기본 복구 목표가 cluster-level restore / PITR / standby seed 중 하나면 continuous WAL archiving + base backup이 기본이다. 도구 선택: - K8s + CNPG → Barman Cloud (내장) - K8s + Crunchy → pgBackRest (내장) - 자체 운영 → pgBackRest 또는 WAL-G `pg_dump`는 다음 용도로 제한: - 선택적 logical export - 로컬/테스트 seed - 일부 schema/table 보존 - migration 검증용 비교 데이터 ### 12. PITR 필요 여부를 초기에 결정 다음 질문에 "예"면 PITR을 우선 검토한다. - 잘못된 migration/DDL을 특정 시점 직전으로 되돌려야 하는가 - 운영 데이터 손실 허용 시간이 짧은가 (RPO < 1h) - 인증 관련 데이터 정합성이 중요한가 ### 13. schema ownership은 서비스별로 분리 기본: - auth-server schema는 auth-server 팀이 소유 - keycloak schema는 keycloak이 소유 - 공용 schema 남발 금지 - "편해서" 하나의 migration 프로젝트로 통합 관리 금지 ### 14. Flyway history table 전략을 먼저 고정 초기에 결정: - `flyway.table` (기본 `flyway_schema_history`) - `flyway.defaultSchema` - `flyway.schemas` - `flyway.createSchemas` - 필요 시 `flyway.initSql` 기본: - history table을 service별 schema에 배치 (예: `auth_server.flyway_schema_history`) - 여러 서비스의 history table을 하나의 schema에 몰지 않음 ### 15. baseline / repair는 예외 절차 baseline과 repair는 정상 운영 흐름이 아니다. 허용 예: - legacy DB를 처음 Flyway 관리로 편입 (baseline) - 의도적 migration 수정 후 공식 절차로 checksum 회복 (repair) - history corruption 복구 (repair) 기본 금지: - CI/CD에서 습관적 baseline/repair - validate 오류를 없애기 위해 무분별하게 repair ### 16. migration은 forward-only를 기본값으로 운영 기본값: - 새 migration으로 앞으로 수정 - rollback용 SQL을 미리 기대하지 않음 - 실패 시 restore/PITR 또는 다음 migration으로 교정 ### 17. Keycloak DB와 auth-server DB는 따로 본다 둘 다 PostgreSQL을 써도 운영 기준은 별도로 둔다. - migration 파이프라인 분리 - backup/restore 영향도 분리 - schema/table ownership 분리 - 버전 업그레이드 절차 분리 - Keycloak은 자체 migration을 내장하므로 **Flyway로 관리하지 않는다** ### 18. test-server는 DB를 기본 전제로 두지 않는다 test-server가 DB 연결이 없으면 migration 대상 아님, DB secret 불필요, rollout 절차도 DB 의존 없이 단순화된다. ### 19. destructive migration은 expand → migrate → contract Zero-downtime을 위한 3단계 릴리즈: 1. **Expand** — 새 컬럼/테이블 추가 (NULL 허용 또는 default 값 있음). 기존 앱 호환. 2. **Migrate** — 앱을 새 스키마 기준으로 배포 + 데이터 backfill. 3. **Contract** — 기존 컬럼/테이블/제약 제거. 한 릴리즈 이상 뒤. 각 단계는 **별도 릴리즈**로 나간다. 같은 릴리즈에서 expand와 contract를 같이 하지 않는다. 인증/권한/토큰 관련 테이블은 특히 보수적으로. ### 20. DB 변경은 애플리케이션 호환성 윈도우를 고려 migration 문서는 다음을 포함한다. - 이전 앱 버전과 호환 여부 - 새 앱 버전과 호환 여부 - 중간 배포 구간에서 허용되는 상태 (N-1 ↔ N 동시 운영 가능 여부) - 롤백 시 DB가 이미 바뀐 상태일 때의 대응 ### 21. 대용량 / long-running DDL은 트랜잭션 밖에서 Postgres에서 다음은 트랜잭션 밖에서 실행해야 한다. - `CREATE INDEX CONCURRENTLY` - `REINDEX CONCURRENTLY` - `ALTER TYPE ... ADD VALUE` (Postgres 12+에서는 트랜잭션 내에서도 제한적으로 가능) - `VACUUM` Flyway에서는 해당 migration 파일 상단에 다음을 적는다: ```sql -- flyway:executeInTransaction=false CREATE INDEX CONCURRENTLY idx_users_email ON users(email); ``` ### 22. restore 테스트 없는 backup/migration 전략 금지 다음은 반드시 drill이 있어야 한다. - PostgreSQL base backup 복구 - WAL/PITR 절차 - Flyway 적용 후 실패 시 중단 및 복구 절차 - Keycloak/auth-server 개별 DB restore 절차 ## 현재 스택 기본 권장안 - **auth-server DB**: CNPG `Cluster` 3 instances, Barman Cloud, RPO 5분, Flyway Job으로 migration - **keycloak DB**: CNPG `Cluster` 별도, Keycloak 자체 migration (Flyway 밖) - **test-server**: DB 없음 - **migration-flyway**: Kubernetes Job (Helm/Argo hook), 앱 Deployment보다 먼저 실행 - **backup**: CNPG Barman continuous WAL + daily base backup, `pg_dump`는 보조 ## 프로젝트 기준 요약 - app rollout과 migration을 분리 (app startup migration 금지) - Postgres on K8s는 CNPG operator를 기본 후보로 - migration Job은 Helm hook 또는 Argo CD sync wave로 app보다 먼저 실행 - migration Job은 `parallelism: 1`, `backoffLimit: 0`, digest pinning, restricted PSA - Flyway undo(U__) 파일 만들지 않음 (OSS 미지원) - validate → migrate → app rollout 순서 - CNPG Barman Cloud (또는 pgBackRest / WAL-G) 물리 백업 + PITR, `pg_dump`는 보조 - schema ownership은 서비스별 분리 - destructive migration은 expand → migrate → contract, 여러 릴리즈에 걸쳐 - non-transactional DDL은 `-- flyway:executeInTransaction=false`