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:
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
## 전제 조건
|
## 전제 조건
|
||||||
|
|
||||||
- REQ-046 ~ REQ-051 코드(BAT-020 PR #17, d4acfd4)가 배포돼 있어야 한다.
|
- **prod는 REL-002(freeze `bd34450`, alembic head `20260605_0025`)** 상태다. BAT-020·BAT-021 코드는 아직 미배포다.
|
||||||
- BAT-021 코드(ensure_schema 갱신, ON CONFLICT 수정)는 **Step4 적용 완료 이후** 배포한다(아래 배포 순서 준수).
|
- **본 절차 시작 시(0단계)** 체크아웃을 origin/main(`9d98d47`)으로 갱신하고 `pip install -e .` 를 실행해 마이그레이션 파일·backfill CLI·신규 코드를 디스크에 올린다. **단 이 시점에는 서비스를 재시작하지 않는다.** 서비스 재시작(신규 코드 활성화)은 Step2 완료 이후(6단계)에 수행한다.
|
||||||
- 마이그레이션 전 DB 백업이 완료돼 있어야 한다.
|
- 마이그레이션 전 DB 백업이 완료돼 있어야 한다.
|
||||||
- 이 런북의 모든 alembic 명령은 `PORTFOLIOHUB_DB_ENABLED=true` 및 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 검증
|
→ Step1 upgrade(20260628_0026) → Step1 검증
|
||||||
→ backfill CLI(pfhub credential backfill-daemon-id) → NULL 0건 확인(게이트)
|
→ backfill CLI(pfhub credential backfill-daemon-id) → NULL 0건 확인(게이트)
|
||||||
→ Step2 upgrade(20260628_0027, 사장님 결재 후) → Step2 검증
|
→ 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 변경 없음) |
|
| **Step2를 backfill 없이 수행** | `alembic upgrade 20260628_0027` 내부 사전 검증 로직이 `daemon_id IS NULL` 행을 감지하고 `RuntimeError`로 자동 거부 (DB 변경 없음) |
|
||||||
| **Step1 없이 Step2 수행** | Step2 revision(`down_revision = 20260628_0026`)이 Step1 적용 여부를 검사하므로 alembic 체인 오류로 실패 |
|
| **Step1 없이 Step2 수행** | Step2 revision(`down_revision = 20260628_0026`)이 Step1 적용 여부를 검사하므로 alembic 체인 오류로 실패 |
|
||||||
| **백업 없이 Step2 수행** | 비가역 PK 변경 후 이슈 발생 시 복원 불가 — 반드시 백업 후 진행 |
|
| **백업 없이 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 백업
|
## 1단계: DB 백업
|
||||||
|
|
||||||
운영 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`:
|
- `app/repositories/postgres.py`:
|
||||||
- `ensure_schema()` broker_credentials: daemon_id NOT NULL, PK=(daemon_id, broker) 정합
|
- `ensure_schema()` broker_credentials: daemon_id NOT NULL, PK=(daemon_id, broker) 정합
|
||||||
- `_row_to_broker_credential()`: Step2 컬럼 순서 (daemon_id 선두)
|
- `_row_to_broker_credential()`: Step2 컬럼 순서 (daemon_id 선두)
|
||||||
@@ -332,8 +362,8 @@ Step2 alembic upgrade 완료 후 코드를 배포한다.
|
|||||||
- `list_broker_credentials()`: fallback 제거, Step2 컬럼 순서
|
- `list_broker_credentials()`: fallback 제거, Step2 컬럼 순서
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# portfolio-hub 서비스 rolling restart (freeze 커밋 9fe6f3b)
|
# portfolio-hub 서비스 재시작 (origin/main 9d98d47 — 코드는 0단계에서 이미 디스크에 있음)
|
||||||
# 배포 방법은 운영 절차에 따름 (예: 컨테이너 재시작, systemd restart 등)
|
# 재시작 방법은 운영 절차에 따름 (예: 컨테이너 재시작, 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 → 중단** |
|
| DB 백업 | 파일 생성 + 크기 > 0 + exit 0 | go / **NO-GO → 중단** |
|
||||||
| alembic current | `20260605_0025 (head)` (Step1 이전 기준) | go / **NO-GO → 팀장 보고** |
|
| alembic current | `20260605_0025 (head)` (Step1 이전 기준) | go / **NO-GO → 팀장 보고** |
|
||||||
| 활성 락 없음 | broker_credentials 대상 활성 쿼리 없음 | 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 재수행 또는 팀장 보고** |
|
| NULL 0건 | `SELECT COUNT(*) ... WHERE daemon_id IS NULL` = 0 | go / **NO-GO → backfill 재수행 또는 팀장 보고** |
|
||||||
| 사장님 결재 | 명시적 결재 확인 | go / **NO-GO → Step2 실행 금지** |
|
| 사장님 결재 | 명시적 결재 확인 | go / **NO-GO → Step2 실행 금지** |
|
||||||
| Step2 검증 | PK=(daemon_id,broker), NOT NULL, user_id 존재, 행 수 일치 | go / **NO-GO → 롤백 또는 DB 복원** |
|
| 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 백업 실패
|
1. 체크아웃 갱신 실패 (마이그레이션 파일 부재, CLI 오류)
|
||||||
2. alembic current가 기대값과 다름
|
2. DB 백업 실패
|
||||||
3. broker_credentials 활성 락 존재 (ops 윈도우 재조정)
|
3. alembic current가 기대값과 다름
|
||||||
4. Step1 upgrade 실패
|
4. broker_credentials 활성 락 존재 (ops 윈도우 재조정)
|
||||||
5. NULL 잔존 (backfill 미완료)
|
5. Step1 upgrade 실패
|
||||||
6. 사장님 결재 미획득 상태에서 Step2 실행 시도
|
6. NULL 잔존 (backfill 미완료)
|
||||||
7. Step2 upgrade 실패
|
7. 사장님 결재 미획득 상태에서 Step2 실행 시도
|
||||||
8. Step2 검증 쿼리 기대값 불일치
|
8. Step2 upgrade 실패
|
||||||
9. 배포 후 헬스체크 실패
|
9. Step2 검증 쿼리 기대값 불일치
|
||||||
|
10. 재시작 후 헬스체크 실패
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 주의사항 요약
|
## 주의사항 요약
|
||||||
|
|
||||||
- Step2 이전에 BAT-021 코드를 배포하면 `ON CONFLICT (daemon_id, broker)` 절이 구 스키마와 맞지 않아 인증정보 upsert 오류 발생.
|
- prod는 REL-002(`bd34450`) 상태로 시작한다. **0단계 체크아웃 갱신(origin/main `9d98d47`)을 먼저 수행하지 않으면 마이그레이션 파일·backfill CLI가 디스크에 없어 이후 단계를 실행할 수 없다.**
|
||||||
- 배포 순서: **Step1 → backfill → NULL 0건 확인 → Step2(결재) → 코드 배포** 순서를 엄수한다.
|
- Step2 이전에 서비스를 재시작하면 `ON CONFLICT (daemon_id, broker)` 절이 구 스키마와 맞지 않아 인증정보 upsert 오류 발생.
|
||||||
|
- 전체 순서: **0단계(체크아웃 갱신, 재시작 X) → DB 백업 → Step1 → backfill → NULL 0건 확인 → Step2(결재) → 서비스 재시작** 순서를 엄수한다.
|
||||||
- `ensure_schema()`는 `IF NOT EXISTS`를 사용하므로 Step2 적용 후 실행 시 테이블이 이미 존재해 스킵됨 (신규 인스턴스 초기화 시에만 적용됨).
|
- `ensure_schema()`는 `IF NOT EXISTS`를 사용하므로 Step2 적용 후 실행 시 테이블이 이미 존재해 스킵됨 (신규 인스턴스 초기화 시에만 적용됨).
|
||||||
- `CREDENTIAL_ENCRYPTION_KEY`가 운영 환경에 설정돼 있지 않으면 인증정보 암·복호화가 동작하지 않는다. 배포 전 반드시 확인.
|
- `CREDENTIAL_ENCRYPTION_KEY`가 운영 환경에 설정돼 있지 않으면 인증정보 암·복호화가 동작하지 않는다. 재시작 전 반드시 확인.
|
||||||
|
|||||||
Reference in New Issue
Block a user