<!-- canonical: https://docs.axelabs.ai/ops/runbook/db-recovery -->
<!-- source: content/ops/runbook/db-recovery.mdx -->

---
title: Frame DB 복구
description: frame-postgres 손상/migration 사고/silent corruption 대응.
playbook: true
---

# Frame DB 복구

## AI 요청 프롬프트

```
https://docs.axelabs.ai/ops/runbook/db-recovery 따라 frame DB 복구 진행해줘.

진행:
1. 현재 사고 진단 — 사용자 보고 증상으로 페이지 "시나리오 분류" 표의 5 시나리오 중 분기 식별 (migration / 사용자 실수 / macmini 손실 / silent corruption / postgres crash)
2. 즉시 대응 — frame container stop (사용자 추가 쓰기 차단) + restic 최신 snapshot 확인
3. 시나리오별 복구 절차 실행, 매 step 결과 받고 다음. destructive (drop database / restore from backup) 직전 사용자 확인
4. 함정 발생 시 페이지 본문 따라 우회 (Phase 5 stub `axe restore` 부재 → restic 직접 / silent corruption 의 부분 복원 범위 / migration rollback)
5. 정합성 검사 통과 (`integrity-check`) + frame 재기동 + (선택) /ops/known-gaps 사고 원인 한 줄
```

본인 AI session = Claude Code / Cursor / ChatGPT 데스크탑 / Claude.app / 기타.

페이지 본문 = 사람이 직접 read 도 가능, AI 도 참고. AI 가 본 페이지 fetch 후 위 진행 순서대로 사용자와 step-by-step interactive 풀어나감.

frame 의 PostgreSQL 데이터가 손상되었을 때 복구 절차.

## 시나리오 분류

| 사건 | 즉시 대응 | 복구 경로 |
|---|---|---|
| Migration 사고 (alembic 적용 후 정합성 깨짐) | 모든 frame container stop | local backup (Tier A) |
| 사용자 실수 (잘못된 분개 대량 등록) | 해당 entity 만 freeze | `reverse_journal` × 다수 OR 백업 |
| macmini 전체 손실 (도난·화재) | 새 macmini 셋업 | ring (Tier B) 또는 cold SSD (Tier C) |
| Silent corruption (검증 실패) | 정합성 검사 결과 분석 | 부분 복원 |
| Postgres 자체 crash | `docker restart frame-postgres` | 거의 자동 복구 |

## 시나리오 1 — Migration 사고

새 migration 적용 후 정합성 검사 실패:

```bash
ssh axe-macmini "docker exec frame-mcp-blue python -m frame.cli integrity-check --entity axec"
# → FAIL: balance mismatch in journal X
```

### 즉시

```bash
# 1. 모든 frame container stop (사용자 추가 쓰기 차단)
ssh axe-macmini "cd /Users/axe/frame && docker compose stop frame-mcp-blue frame-mcp-green"

# 2. Backup 최근 snapshot 확인
ssh axe-macmini "
  restic -r /Users/axe/.axe/backups/local snapshots \
    --password-file &lt;(security find-generic-password -w -s axe.backup.restic.local)
"
# 어제 03:00 KST snapshot 찾기
```

### 복구

```bash
# 3. 어제 backup 으로 frame-postgres 복원
# `axe restore` 는 Phase 5 stub 상태 → 현재는 restic 직접 호출:
ssh axe-macmini "
  restic -r /Users/axe/.axe/backups/local \
    --password-file &lt;(security find-generic-password -w -s axe.backup.restic.local -a axe-cli) \
    restore &lt;snapshot_id&gt; --target /tmp/restore --include 'frame-postgres/*'
"
# 그 후 docker exec frame-postgres pg_restore 또는 dump replay
# 정식 `axe restore` 자동화는 Phase 5 (D) 작업 항목

# 4. frame container restart (이전 코드 버전으로!)
ssh axe-macmini "cd /Users/axe/frame && git checkout &lt;previous-good-commit&gt;"
ssh axe-macmini "
  cd /Users/axe/frame &&
  set -a && source .env.local && set +a &&
  docker compose build frame-mcp-blue &&
  docker compose up -d --force-recreate frame-mcp-blue frame-mcp-green
"

# 5. 정합성 재확인
ssh axe-macmini "docker exec frame-mcp-blue python -m frame.cli integrity-check --entity axec"
# → OK
```

### 손실 분석

복원 후 손실 = 백업 시점 (어제 03:00) ~ container stop 시점 사이의 모든 쓰기.

```bash
# 손실된 audit_log 추출 (다른 백업 또는 in-memory)
ssh axe-macmini "docker exec frame-postgres psql -U frame -d frame -c \"
  SELECT * FROM axec.audit_log
  WHERE ts > '2026-05-20 03:00:00+09:00'
  ORDER BY ts;
\""
```

사용자에게 통지 + 수동 재입력 또는 잘못 적용된 migration 의 의도된 효과 분석.

## 시나리오 2 — 사용자 실수

회계사가 실수로 잘못된 분개 50건 등록:

### 우선 — `reverse_journal` 시도

```python
# Claude Code 에서
Frame:list_journals entity_id=axec, start_date=2026-05-20, status=posted

# 잘못된 journal_id 목록 확인 후
for jid in [...]:
    Frame:reverse_journal entity_id=axec, journal_id=jid, memo="실수 역분"
```

`reverse_journal` 은 새 journal 생성 (append-only). audit_log 에 모두 기록.

### 대량 (50건+) — 백업 복원

50건 넘으면 reverse 절차도 부담. 백업 복원이 더 깔끔:

```bash
# 1. 사용자의 다른 활동 멈추기 (claude.ai connector 비활성 요청)

# 2. 가장 가까운 valid backup 복원 — table-level restore 자동화 TBD
# 현재는 dump 전체 복원 후 손상 table 만 selective merge
# (정식 도구화 = Phase 5 D 항목)
```

table-level 복원이 가능한지 확인 필요 — restic 자체는 file-level 만 → pg_dump 의 selective restore 필요. 향후 도구화 검토.

## 시나리오 3 — macmini 전체 손실

가장 심각. 새 macmini 에 처음부터 셋업.

### 절차

```bash
# 1. 새 macmini 준비
# - macOS 클린 install
# - Tailscale 가입 + 운영자 key 등록
# - Docker Desktop install
# - axe CLI install: bash &lt;(curl -sSL https://docs.axelabs.ai/install/axe-cli.sh)

# 2. customers.yaml 의 entry 그대로 사용
# (운영자 머신에서 push)

# 3. 가장 신선한 백업 복원 (restic 직접 호출)
# ring peer (realchoice macmini) 측에서 pull:
ssh axe-macmini "
  restic -r sftp:realchoice@realchoice-macmini.tail090015.ts.net:/Users/realchoice/peer-backups/axe \
    --password-file &lt;(security find-generic-password -w -s axe.backup.restic.local -a axe-cli) \
    restore latest --target /tmp/restore
"

# 또는 cold SSD 마운트 후
ssh axe-macmini "
  restic -r /Volumes/axe-cold-1/restic-repo \
    --password-file &lt;(종이메모) \
    restore latest --target /tmp/restore
"

# 4. frame onboard (axe CLI 의 `--skip-azure` 플래그 는 향후 추가 예정;
#    현재는 onboard 가 Azure 측 변경 자체를 하지 않으므로 일반 onboard 그대로)
axe onboard axe --apply

# 5. Microsoft 측 — redirect_uri 변경 불필요 (axe.axelabs.ai 도메인 그대로)

# 6. 검증
axe health axe
ssh axe-macmini "docker exec frame-mcp-blue python -m frame.cli integrity-check --entity axec"
ssh axe-macmini "docker exec frame-mcp-blue python -m frame.cli integrity-check --entity axev"

# 7. 직원 통지
"frame 시스템 복구 완료. claude.ai 의 connector 는 그대로 작동합니다."
```

## 시나리오 4 — Silent Corruption

매일 자동 `integrity-check` 가 잡아냄. 발견 즉시:

1. 어느 entity, 어느 검사가 실패했는지 확인
2. 가장 가까운 valid snapshot 찾기 (전날 또는 그 이전)
3. table-level 또는 entity-level 복원
4. 사용자에게 통지 (해당 기간 데이터 재입력 필요할 수도)

## Restore Drill (분기별)

매 분기 (Jan/Apr/Jul/Oct 15) 자동 drill 이 실행:

1. 임시 staging 컨테이너 (`frame-mcp-drill`) 에 어제 backup 복원
2. `integrity-check` 4개 모두 통과 여부
3. 결과 → 운영자 Slack

drill 실패 = backup 자체 문제 (압축 손상, password 오류 등). 즉시 원인 분석.

## 함정

| 함정 | 결과 | 회피 |
|---|---|---|
| 손상 발견 후 frame 미정지 → 추가 쓰기 누적 | 손실 확대 | 즉시 `docker compose stop` |
| ring peer 의 backup 도 손상되었을 가능성 | 양쪽 손상 | cold SSD 도 보유 |
| 복원 후 정합성 검사 안 함 | 잠재 손상 미발견 | `integrity-check` 필수 |
| pg_dump 의 schema-per-entity 옵션 누락 | 복원 시 entity schema 사라짐 | restic 의 raw file 복원 (PGDATA volume) |

## ⚠️ 현재 도구 상태

`axe restore --customer ... --tier ... --target ... --table ... --apply` 식의 통합 CLI 는 **Phase 5 (D) 작업 항목** — 현재 stub 상태. 본 runbook 의 실제 복구는 위에서처럼 **restic 직접 호출**.

자동화 완성 후 본 runbook 의 명령어 갱신 예정 — 그때까지 운영자는 restic CLI 직접 + docker exec 조합 사용.
