309 lines
13 KiB
Markdown
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`
|