docs(runbook): Step2 마이그레이션 배포 순서 정정 — 체크아웃 갱신 선행

운영팀 지적: prod=REL-002(bd34450)라 마이그레이션 파일·backfill CLI 부재.
0단계 '체크아웃 갱신(origin/main, 디스크, 재시작X)'를 마이그레이션 앞에 추가,
서비스 재시작은 Step2 후로 분리. freeze 9fe6f3b→9d98d47, 실패모드/게이트 보강.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-30 12:58:01 +09:00
co-authored by Claude Opus 4.8
parent 9d98d47e2d
commit 7ba618eb30
@@ -10,8 +10,8 @@
## 전제 조건
- REQ-046 ~ REQ-051 코드(BAT-020 PR #17, d4acfd4)가 배포돼 있어야 한다.
- BAT-021 코드(ensure_schema 갱신, ON CONFLICT 수정)는 **Step4 적용 완료 이후** 배포한다(아래 배포 순서 준수).
- **prod는 REL-002(freeze `bd34450`, alembic head `20260605_0025`)** 상태다. BAT-020·BAT-021 코드는 아직 미배포다.
- **본 절차 시작 시(0단계)** 체크아웃을 origin/main(`9d98d47`)으로 갱신하고 `pip install -e .` 를 실행해 마이그레이션 파일·backfill CLI·신규 코드를 디스크에 올린다. **단 이 시점에는 서비스를 재시작하지 않는다.** 서비스 재시작(신규 코드 활성화)은 Step2 완료 이후(6단계)에 수행한다.
- 마이그레이션 전 DB 백업이 완료돼 있어야 한다.
- 이 런북의 모든 alembic 명령은 `PORTFOLIOHUB_DB_ENABLED=true` 및 DB 접속 환경변수가 설정된 셸에서 실행한다.
@@ -20,11 +20,12 @@
## 배포 순서 개요
```
DB 백업 → 프리체크(alembic head·NULL 확인·락 확인)
0단계: 체크아웃 갱신(origin/main 9d98d47, pip install -e ., 서비스 재시작 X)
→ DB 백업 → 프리체크(alembic head·NULL 확인·락 확인)
→ Step1 upgrade(20260628_0026) → Step1 검증
→ backfill CLI(pfhub credential backfill-daemon-id) → NULL 0건 확인(게이트)
→ Step2 upgrade(20260628_0027, 사장님 결재 후) → Step2 검증
→ BAT-021 코드 배포(rolling restart)
→ 서비스 재시작(코드는 0단계에서 이미 디스크에 있음 → Step2 후 재시작으로 신규 코드 활성화)
→ 배포 후 동작 확인
```
@@ -34,13 +35,42 @@ DB 백업 → 프리체크(alembic head·NULL 확인·락 확인)
| 위반 | 증상 |
| --- | --- |
| **코드 배포를 Step2보다 먼저 수행** | `upsert_broker_credential`의 `ON CONFLICT (daemon_id, broker)` 절이 구 PK `(user_id, broker)`와 불일치 → 인증정보 등록·회전 시 `psycopg.errors.UndefinedColumn` 또는 `ProgrammingError` 발생 |
| **체크아웃 미갱신 상태로 `alembic upgrade` 시도** | revision `20260628_0026` 파일이 디스크에 없으므로 alembic이 head를 못 찾거나 `Can't locate revision` 오류로 실패 |
| **서비스 재시작을 Step2보다 먼저 수행** | `upsert_broker_credential`의 `ON CONFLICT (daemon_id, broker)` 절이 구 PK `(user_id, broker)`와 불일치 → 인증정보 등록·회전 시 `psycopg.errors.UndefinedColumn` 또는 `ProgrammingError` 발생 |
| **Step2를 backfill 없이 수행** | `alembic upgrade 20260628_0027` 내부 사전 검증 로직이 `daemon_id IS NULL` 행을 감지하고 `RuntimeError`로 자동 거부 (DB 변경 없음) |
| **Step1 없이 Step2 수행** | Step2 revision(`down_revision = 20260628_0026`)이 Step1 적용 여부를 검사하므로 alembic 체인 오류로 실패 |
| **백업 없이 Step2 수행** | 비가역 PK 변경 후 이슈 발생 시 복원 불가 — 반드시 백업 후 진행 |
---
## 0단계: 체크아웃 갱신 (서비스 재시작 없음)
prod 서버의 portfolio-hub 체크아웃을 origin/main(`9d98d47`)으로 갱신한다. 이 작업은 **실행 중 프로세스를 바꾸지 않는다** — 파이썬 프로세스는 재시작 전까지 메모리의 구 코드를 그대로 실행한다. 갱신 목적은 마이그레이션 파일·backfill CLI·신규 앱코드를 디스크에 올려 이후 alembic 명령과 CLI가 실행 가능하게 하는 것이다.
```bash
cd /path/to/portfolio-hub
# origin/main 최신 상태로 체크아웃 갱신
git fetch origin
git checkout 9d98d47 # 또는: git reset --hard origin/main
# 의존성 및 패키지 재설치 (마이그레이션 파일·CLI entrypoint 포함)
pip install -e .
# 마이그레이션 파일 존재 확인
ls app/db/migrations/versions/20260628_0026_*.py
ls app/db/migrations/versions/20260628_0027_*.py
# 기대: 두 파일 모두 존재
# backfill CLI 확인
pfhub credential backfill-daemon-id --help
# 기대: usage 출력 (에러 없음)
```
> **서비스 재시작 금지**: 이 단계에서는 서비스(컨테이너·프로세스)를 재시작하지 않는다. 구 프로세스는 Step1·backfill 동안 구 PK `(user_id, broker)` 스키마와 호환된 상태로 정상 동작한다. 서비스 재시작은 Step2 완료 이후(6단계)에만 수행한다.
---
## 1단계: DB 백업
운영 DB에 마이그레이션을 적용하기 전 전체 스냅샷을 백업한다.
@@ -320,11 +350,11 @@ pg_restore -h <DB_HOST> -U <DB_USER> -d <DB_NAME> -c \
---
## 6단계: BAT-021 코드 배포 (Step2 완료 이후)
## 6단계: 서비스 재시작 (Step2 완료 이후)
Step2 alembic upgrade 완료 후 코드를 배포한다.
Step2 alembic upgrade 완료 후 서비스를 재시작한다. **코드·마이그레이션 파일은 0단계에서 이미 디스크에 올라 있다.** 재시작은 메모리의 구 프로세스를 내리고 디스크의 신규 코드(`9d98d47`)를 활성화하는 것이 목적이다.
포함 변경:
신규 코드에 포함된 변경(재시작 후 활성화됨):
- `app/repositories/postgres.py`:
- `ensure_schema()` broker_credentials: daemon_id NOT NULL, PK=(daemon_id, broker) 정합
- `_row_to_broker_credential()`: Step2 컬럼 순서 (daemon_id 선두)
@@ -332,8 +362,8 @@ Step2 alembic upgrade 완료 후 코드를 배포한다.
- `list_broker_credentials()`: fallback 제거, Step2 컬럼 순서
```bash
# portfolio-hub 서비스 rolling restart (freeze 커밋 9fe6f3b)
# 배포 방법은 운영 절차에 따름 (예: 컨테이너 재시작, systemd restart 등)
# portfolio-hub 서비스 재시작 (origin/main 9d98d47 — 코드는 0단계에서 이미 디스크에 있음)
# 재시작 방법은 운영 절차에 따름 (예: 컨테이너 재시작, systemd restart 등)
```
---
@@ -356,6 +386,7 @@ pfhub credential list --user <username> --daemon-id <daemon_slug>
| 게이트 | 조건 | 판단 |
| --- | --- | --- |
| 체크아웃 갱신 | `20260628_0026`·`0027` 파일 존재, `pfhub credential backfill-daemon-id --help` 정상 | go / **NO-GO → 0단계 재수행** |
| DB 백업 | 파일 생성 + 크기 > 0 + exit 0 | go / **NO-GO → 중단** |
| alembic current | `20260605_0025 (head)` (Step1 이전 기준) | go / **NO-GO → 팀장 보고** |
| 활성 락 없음 | broker_credentials 대상 활성 쿼리 없음 | go / **NO-GO → 대기 또는 재조정** |
@@ -363,26 +394,28 @@ pfhub credential list --user <username> --daemon-id <daemon_slug>
| NULL 0건 | `SELECT COUNT(*) ... WHERE daemon_id IS NULL` = 0 | go / **NO-GO → backfill 재수행 또는 팀장 보고** |
| 사장님 결재 | 명시적 결재 확인 | go / **NO-GO → Step2 실행 금지** |
| Step2 검증 | PK=(daemon_id,broker), NOT NULL, user_id 존재, 행 수 일치 | go / **NO-GO → 롤백 또는 DB 복원** |
| 배포 후 헬스 | GET /health → 200, db_enabled=true | go / **NO-GO → 코드 롤백** |
| 재시작 후 헬스 | GET /health → 200, db_enabled=true | go / **NO-GO → 구 커밋으로 체크아웃 후 재시작, 팀장 보고** |
### 중단 기준 요약
이 중 하나라도 발생하면 즉시 해당 단계를 중단하고 팀장에게 보고한다:
1. DB 백업 실패
2. alembic current가 기대값과 다름
3. broker_credentials 활성 락 존재 (ops 윈도우 재조정)
4. Step1 upgrade 실패
5. NULL 잔존 (backfill 미완료)
6. 사장님 결재 미획득 상태에서 Step2 실행 시도
7. Step2 upgrade 실패
8. Step2 검증 쿼리 기대값 불일치
9. 배포 후 헬스체크 실패
1. 체크아웃 갱신 실패 (마이그레이션 파일 부재, CLI 오류)
2. DB 백업 실패
3. alembic current가 기대값과 다름
4. broker_credentials 활성 락 존재 (ops 윈도우 재조정)
5. Step1 upgrade 실패
6. NULL 잔존 (backfill 미완료)
7. 사장님 결재 미획득 상태에서 Step2 실행 시도
8. Step2 upgrade 실패
9. Step2 검증 쿼리 기대값 불일치
10. 재시작 후 헬스체크 실패
---
## 주의사항 요약
- Step2 이전에 BAT-021 코드를 배포하면 `ON CONFLICT (daemon_id, broker)` 절이 구 스키마와 맞지 않아 인증정보 upsert 오류 발생.
- 배포 순서: **Step1 → backfill → NULL 0건 확인 → Step2(결재) → 코드 배포** 순서를 엄수한다.
- prod는 REL-002(`bd34450`) 상태로 시작한다. **0단계 체크아웃 갱신(origin/main `9d98d47`)을 먼저 수행하지 않으면 마이그레이션 파일·backfill CLI가 디스크에 없어 이후 단계를 실행할 수 없다.**
- Step2 이전에 서비스를 재시작하면 `ON CONFLICT (daemon_id, broker)` 절이 구 스키마와 맞지 않아 인증정보 upsert 오류 발생.
- 전체 순서: **0단계(체크아웃 갱신, 재시작 X) → DB 백업 → Step1 → backfill → NULL 0건 확인 → Step2(결재) → 서비스 재시작** 순서를 엄수한다.
- `ensure_schema()`는 `IF NOT EXISTS`를 사용하므로 Step2 적용 후 실행 시 테이블이 이미 존재해 스킵됨 (신규 인스턴스 초기화 시에만 적용됨).
- `CREDENTIAL_ENCRYPTION_KEY`가 운영 환경에 설정돼 있지 않으면 인증정보 암·복호화가 동작하지 않는다. 배포 전 반드시 확인.
- `CREDENTIAL_ENCRYPTION_KEY`가 운영 환경에 설정돼 있지 않으면 인증정보 암·복호화가 동작하지 않는다. 재시작 전 반드시 확인.