From 7ba618eb303e6379704849d8eeaba4ebe1dad98e Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Tue, 30 Jun 2026 12:58:01 +0900 Subject: [PATCH] =?UTF-8?q?docs(runbook):=20Step2=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20=EB=B0=B0=ED=8F=AC=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EC=A0=95=EC=A0=95=20=E2=80=94=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=EC=95=84=EC=9B=83=20=EA=B0=B1=EC=8B=A0=20=EC=84=A0?= =?UTF-8?q?=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 운영팀 지적: prod=REL-002(bd34450)라 마이그레이션 파일·backfill CLI 부재. 0단계 '체크아웃 갱신(origin/main, 디스크, 재시작X)'를 마이그레이션 앞에 추가, 서비스 재시작은 Step2 후로 분리. freeze 9fe6f3b→9d98d47, 실패모드/게이트 보강. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5 --- .../bat-021-req047-step2-migration-runbook.md | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/docs/runbooks/bat-021-req047-step2-migration-runbook.md b/docs/runbooks/bat-021-req047-step2-migration-runbook.md index a58930d..3cb54c2 100644 --- a/docs/runbooks/bat-021-req047-step2-migration-runbook.md +++ b/docs/runbooks/bat-021-req047-step2-migration-runbook.md @@ -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 -U -d -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 --daemon-id | 게이트 | 조건 | 판단 | | --- | --- | --- | +| 체크아웃 갱신 | `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 --daemon-id | 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`가 운영 환경에 설정돼 있지 않으면 인증정보 암·복호화가 동작하지 않는다. 재시작 전 반드시 확인.