Files
project-infra/docs/standards/infra/db-and-migration.md
T

309 lines
13 KiB
Markdown

# 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`