feat(bat-021): REQ-047 Step2 동반 코드 수정 + 실 DB 통합검증 + 런북
- ensure_schema() broker_credentials: daemon_id NOT NULL, PK=(daemon_id,broker)로 갱신 - _row_to_broker_credential: Step2 컬럼 순서(daemon_id 선두)로 갱신 - upsert_broker_credential: ON CONFLICT (daemon_id,broker)로 수정, fallback try/except 제거 - list_broker_credentials: fallback 제거, SELECT 컬럼 순서 갱신 - test_bat020_broker_credential_domain.py: AC-3/AC-4 행 순서를 Step2 포맷으로 갱신 - tests/test_bat021_step2_ensure_schema_and_upsert.py: 12개 신규 테스트 - docs/runbooks/bat-021-req047-step2-migration-runbook.md: 운영 적용 런북 - docs/batches/bat-021: batch 문서(index/planning/design/implementation/verification) 실 PostgreSQL 통합검증(AC-1~7 전건 PASS): Step1 upgrade → backfill → Step2 upgrade → downgrade 시나리오 포함 NULL 사전검증(upgrade 거부) 및 downgrade 안전장치(중복 시 RuntimeError) 확인 521 passed, 0 failed (기존 509 + 신규 12)
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# 운영 적용 런북: REQ-047 Step2 — broker_credentials PK 전환 (daemon_id, broker)
|
||||
|
||||
- Batch: BAT-021
|
||||
- Discovery: dcy-010
|
||||
- Date: 2026-06-28
|
||||
- 결재 게이트: 사장님 결재 필수 (비가역 PK 변경)
|
||||
|
||||
## 전제 조건
|
||||
|
||||
- REQ-046 ~ REQ-051 코드(BAT-020 PR #17, d4acfd4)가 배포돼 있어야 한다.
|
||||
- BAT-021 코드(ensure_schema 갱신, ON CONFLICT 수정)는 **Step2 적용 완료 이후** 배포한다(아래 배포 순서 준수).
|
||||
- 마이그레이션 전 DB 백업이 완료돼 있어야 한다.
|
||||
|
||||
## 배포 순서 개요
|
||||
|
||||
```
|
||||
백업 → Step1 upgrade → backfill CLI → NULL 0건 확인 → Step2 upgrade(결재) → 코드 배포 → 동작 확인 → 비상 시 롤백
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1단계: DB 백업
|
||||
|
||||
운영 DB에 마이그레이션을 적용하기 전 전체 스냅샷을 백업한다.
|
||||
|
||||
```bash
|
||||
# 예: pg_dump 사용
|
||||
pg_dump -h <DB_HOST> -U <DB_USER> -d <DB_NAME> -F c -f backup_pre_bat021_$(date +%Y%m%d%H%M%S).dump
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2단계: Step1 alembic upgrade (daemon_id NULL 컬럼 추가)
|
||||
|
||||
```bash
|
||||
export TIMESCALEDB_HOST=<DB_HOST>
|
||||
export TIMESCALEDB_PORT=<DB_PORT>
|
||||
export TIMESCALEDB_NAME=<DB_NAME>
|
||||
export TIMESCALEDB_USER=<DB_USER>
|
||||
export TIMESCALEDB_PASSWORD=<DB_PASS>
|
||||
export PORTFOLIOHUB_DB_ENABLED=true
|
||||
|
||||
alembic upgrade 20260628_0026
|
||||
```
|
||||
|
||||
예상 출력:
|
||||
```
|
||||
INFO [alembic.runtime.migration] Running upgrade <prev> -> 20260628_0026, broker_credentials: daemon_id TEXT NULL 컬럼 추가 (REQ-047 Step1)
|
||||
```
|
||||
|
||||
### 검증
|
||||
|
||||
```sql
|
||||
-- broker_credentials에 daemon_id TEXT NULL 컬럼이 존재해야 함
|
||||
\d broker_credentials
|
||||
-- 또는
|
||||
SELECT column_name, is_nullable FROM information_schema.columns
|
||||
WHERE table_name = 'broker_credentials' AND column_name = 'daemon_id';
|
||||
-- 기대: daemon_id | YES (nullable)
|
||||
|
||||
-- 기존 PK (user_id, broker) 유지 확인
|
||||
SELECT constraint_name, constraint_type FROM information_schema.table_constraints
|
||||
WHERE table_name = 'broker_credentials' AND constraint_type = 'PRIMARY KEY';
|
||||
-- 기대: broker_credentials_pkey | PRIMARY KEY
|
||||
|
||||
-- 기존 데이터 daemon_id=NULL 보존 확인
|
||||
SELECT COUNT(*) FROM broker_credentials; -- 기존 행 수 유지
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3단계: backfill CLI — daemon_id 매핑
|
||||
|
||||
모든 기존 broker_credentials 행에 daemon_id를 설정한다.
|
||||
|
||||
```bash
|
||||
# 사용자별로 실행 (daemon_id는 해당 user의 daemon id)
|
||||
pfhub credential backfill-daemon-id --user <username> --daemon-id <daemon_slug>
|
||||
|
||||
# --dry-run으로 먼저 확인
|
||||
pfhub credential backfill-daemon-id --user <username> --daemon-id <daemon_slug> --dry-run
|
||||
```
|
||||
|
||||
### 검증: NULL 0건 확인
|
||||
|
||||
```sql
|
||||
-- 이 쿼리가 0을 반환해야 Step2를 진행할 수 있다
|
||||
SELECT COUNT(*) FROM broker_credentials WHERE daemon_id IS NULL;
|
||||
-- 기대: 0
|
||||
```
|
||||
|
||||
**경고: NULL이 1건이라도 있으면 Step2 upgrade가 RuntimeError로 거부된다. 반드시 0건 확인 후 진행.**
|
||||
|
||||
---
|
||||
|
||||
## 4단계: Step2 alembic upgrade (PK 전환) — 사장님 결재 필요
|
||||
|
||||
이 단계는 비가역 PK 변경이다. **사장님 결재 후에만 실행한다.**
|
||||
|
||||
```bash
|
||||
alembic upgrade 20260628_0027
|
||||
```
|
||||
|
||||
예상 출력:
|
||||
```
|
||||
INFO [alembic.runtime.migration] Running upgrade 20260628_0026 -> 20260628_0027, broker_credentials: PK (user_id,broker) → (daemon_id,broker) 전환 (REQ-047 Step2)
|
||||
```
|
||||
|
||||
### 검증: Step2 완료 확인
|
||||
|
||||
```sql
|
||||
-- PK가 (daemon_id, broker)로 변경됐는지 확인
|
||||
SELECT constraint_name FROM information_schema.table_constraints
|
||||
WHERE table_name = 'broker_credentials' AND constraint_type = 'PRIMARY KEY';
|
||||
-- 기대: broker_credentials_pkey
|
||||
|
||||
SELECT pg_get_constraintdef(oid)
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'broker_credentials_pkey';
|
||||
-- 기대: PRIMARY KEY (daemon_id, broker)
|
||||
|
||||
-- daemon_id NOT NULL 확인
|
||||
SELECT column_name, is_nullable FROM information_schema.columns
|
||||
WHERE table_name = 'broker_credentials' AND column_name = 'daemon_id';
|
||||
-- 기대: daemon_id | NO (not nullable)
|
||||
|
||||
-- user_id 컬럼 유지 확인
|
||||
SELECT column_name FROM information_schema.columns
|
||||
WHERE table_name = 'broker_credentials' AND column_name = 'user_id';
|
||||
-- 기대: user_id 존재
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5단계: 코드 배포 (BAT-021 코드 포함)
|
||||
|
||||
Step2 alembic upgrade가 완료된 후에 코드를 배포한다.
|
||||
|
||||
포함 변경:
|
||||
- `app/repositories/postgres.py`:
|
||||
- `ensure_schema()` broker_credentials: daemon_id NOT NULL, PK=(daemon_id, broker)
|
||||
- `_row_to_broker_credential()`: Step2 컬럼 순서 (daemon_id 선두)
|
||||
- `upsert_broker_credential()`: ON CONFLICT (daemon_id, broker), fallback 제거
|
||||
- `list_broker_credentials()`: fallback 제거, Step2 컬럼 순서
|
||||
|
||||
---
|
||||
|
||||
## 6단계: 동작 확인
|
||||
|
||||
```bash
|
||||
# pfhub CLI로 인증정보 조회/등록 테스트
|
||||
pfhub credential list --user <username> --daemon-id <daemon_id>
|
||||
|
||||
# 서비스 헬스체크
|
||||
curl http://<SERVICE_HOST>/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 롤백 절차
|
||||
|
||||
### Step2 롤백 (alembic downgrade)
|
||||
|
||||
Step2 downgrade는 (user_id, broker) 중복이 없는 경우에만 가능하다.
|
||||
|
||||
```bash
|
||||
# Step2 → Step1 상태로 복원
|
||||
alembic downgrade 20260628_0026
|
||||
```
|
||||
|
||||
**경고**: 같은 user의 여러 daemon이 동일 broker를 사용하는 경우 downgrade가 RuntimeError로 거부된다.
|
||||
이 경우 수동 데이터 정리 후 downgrade하거나 백업에서 복원한다.
|
||||
|
||||
### Step1 롤백
|
||||
|
||||
```bash
|
||||
# Step1 → 이전 상태로 복원 (daemon_id 컬럼 제거)
|
||||
alembic downgrade 20260605_0025
|
||||
```
|
||||
|
||||
### DB 복원 (최후 수단)
|
||||
|
||||
```bash
|
||||
pg_restore -h <DB_HOST> -U <DB_USER> -d <DB_NAME> -c backup_pre_bat021_<TIMESTAMP>.dump
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 주의사항
|
||||
|
||||
- Step2 이전에 BAT-021 코드를 배포하면 `ON CONFLICT (daemon_id, broker)` 절이 구 스키마와 맞지 않아 오류 발생.
|
||||
- 배포 순서: Step1 → backfill → Step2 → 코드 배포 순서를 엄수한다.
|
||||
- `ensure_schema()`는 `IF NOT EXISTS`를 사용하므로 Step2 적용 후 실행 시 테이블이 이미 존재해 스킵됨 (신규 인스턴스 초기화 시에만 적용됨).
|
||||
Reference in New Issue
Block a user