Files
ewoooc/docs/phase1_db_design_20260503.md
OoO 4648673423 db(p1): ai_calls/mcp_calls/budgets schema + bge-m3 signature
migrations 024/025/026 — 統一 LLM 遙測 + 預算告警 + RAG 一致性護欄
- 024: ai_calls 表 + 5 索引 + 6 CHECK constraint(H1/H2/M3/L3)
- 025: mcp_calls + ai_call_budgets + 10 種子預算(含 ollama_secondary)
- 026: ai_insights.embedding_signature + pgcrypto + CONCURRENTLY index

A11 critic 三輪審查記錄完整保留:
- Phase 1 schema review: 2 BLOCKER + 4 HIGH + 6 MEDIUM 全處理
- Phase 1 final sign-off: 0 BLOCKER + 2 HIGH + 4 MEDIUM
- Phase 6 ADR review: 5 BLOCKER + 6 HIGH 全修

Operation Ollama-First v5.0 / Phase 0+1+6 護欄

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:04:42 +08:00

316 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 1 DB Design — Operation Ollama-First v5.0
> **日期**2026-05-03
> **作者**A3 db-expert
> **產出**3 個 migration024/025/026+ 設計理由 + 效能評估
> **依據**`docs/phase0_audit_report_20260503.md` 34 個 LLM 呼叫點 / 11.8% 覆蓋率
> **狀態**SQL 檔已產出於 `migrations/`**未自動 apply**,待統帥 review 後手動執行
---
## TL;DR
| 交付物 | 路徑 | 影響 |
|--------|------|------|
| `ai_calls` 統一 LLM 遙測表 | `migrations/024_create_ai_calls_table.sql` | 接 30 個未覆蓋呼叫點 |
| `mcp_calls` MCP 遙測表 | `migrations/025_create_mcp_calls_and_budgets.sql` | Phase 10 預備 |
| `ai_call_budgets` 預算閾值 | 同上(含 5 筆種子) | Phase 9 預算告警 |
| `ai_insights.embedding_signature` | `migrations/026_add_embedding_signature.sql` | BGE-M3 一致性護欄 |
**結論**Schema 設計已完備,無 schema 衝突。**A4 接 logger 工作可立即啟動**,唯一前置條件是統帥手動 apply 這 3 個 migration。
---
## Section 1 — Schema 設計理由
### 1.1 ai_calls 欄位選擇邏輯
| 欄位 | 為何必要 | 為何這個型別 |
|------|---------|-------------|
| `id BIGSERIAL` | 90 天 ~6.5M,年累積會超 INT4 21 億的 3% — 提早用 BIGSERIAL 避免將來改型別 | 與 mcp_calls 一致 |
| `called_at TIMESTAMPTZ` | 報表查詢核心欄位 | 用 TIMESTAMPTZ不是 TIMESTAMP因為 momo 三主機跨時區GCP UTC / 188 Asia/Taipei |
| `caller VARCHAR(64)` | 必白名單管控;新增需 ADR | 64 足夠(最長 `code_review_elephant` 20 字) |
| `provider VARCHAR(32)` | A1 audit 列舉的 7 種主機標籤 | 32 足夠 |
| `model VARCHAR(128)` | NIM 模型名可達 50+(如 `nvidia/llama-3.3-nemotron-super-49b-v1.5` | 128 留 buffer |
| `input_tokens / output_tokens` | Token 日報核心NOT NULL DEFAULT 0 確保 SUM() 不爆 | INT 足夠(單次最大 200K一年累積一個 caller 也只到 ~10BINT4 上限 21 億夠) |
| `duration_ms INT` | 監控 LLM 慢查;可為 NULLAiderHeal 走 SSH 拿不到精確值) | INT |
| `status` | ok/fallback/error/timeout/cache_only — 串接 fallback 鏈關鍵 | VARCHAR(16) |
| `fallback_to` | 「主路徑失敗,下游 caller」串接邏輯下游本身另寫一筆 | VARCHAR(64) 同 caller |
| `cost_usd NUMERIC(10,6)` | Phase 9 預算用6 位小數可記到 $0.000001OpenRouter 細粒計費需要) | NUMERIC 不用 FLOAT避免累計誤差 |
| `cache_hit BOOLEAN` | Anthropic prompt cache / Gemini cache成本降 90%)必追蹤 | 預設 FALSE |
| `rag_hit BOOLEAN` | Phase 11 RAG 攔截率核心 KPI | 預設 FALSE |
| `request_id VARCHAR(64)` | Code Review 三鏈、Q&A fallback 三層必須 trace 同一邏輯請求 | UUID4 takes 36, 加 prefix 也夠 |
| `error TEXT` | 錯誤原文,可長 | TEXT |
| `meta JSONB` | prompt_hash, temperature, top_p, fingerprint, embedding_signature 等彈性擴展 | JSONB非 JSON支援索引 |
### 1.2 索引設計理由5 個)
| Idx | Cols | 用途 | partial? |
|-----|------|------|---------|
| `idx_ai_calls_called_at` | (called_at DESC) | 全表時間切片,日報週報必用 | 否 |
| `idx_ai_calls_caller_called_at` | (caller, called_at DESC) | TOP caller / 單 caller 趨勢 | 否 |
| `idx_ai_calls_provider_called_at` | (provider, called_at DESC) | by provider 統計 / 預算追蹤 | 否 |
| `idx_ai_calls_request_id` | (request_id) | trace 單一 request 全鏈 | **WHERE request_id IS NOT NULL** |
| `idx_ai_calls_status_called_at` | (status, called_at DESC) | 異常監控 | **WHERE status <> 'ok'**90%+ 是 okpartial 大幅縮體) |
**未建立的索引**
- `meta JSONB` 的 GIN index — V1 不建。GIN 寫入放大 ~3-5x且尚未確定查詢 patternPhase 5 報表穩定後再評估。
- `model` 單欄索引 — 報表需求都會帶 called_at已含 idx_ai_calls_called_at再加 `(model, called_at)` 在 V1 邊際效益低。
### 1.3 是否 partition by called_at — 決策:**V1 不分區**
| 評估面 | 數字 | 結論 |
|--------|------|------|
| 月寫入量 | 50 ins/min × 60 × 24 × 30 ≈ 2.16M | 中等 |
| 90 天保留量 | ~6.5M | PostgreSQL 14 單表健康範圍 |
| 索引大小估算5 個) | ~800MB | 在 momo-db 容器資源內 |
| Partition 維護成本 | 須 cron 自動 CREATE 下月 partition + DROP 過期 | **+1 維護負擔** |
**決策**V1 不分區,但留好觸發升級條件:
- **觸發升級門檻**:月寫入超 5M、單表超 30M、或日報查詢 latency p95 > 500ms
- **升級路徑**DECLARATIVE PARTITIONING by RANGE(called_at) monthly配合 `pg_partman`
### 1.4 保留策略 — 90 天 hot dataDELETE 不 archive
| 選項 | 優劣 | 結論 |
|------|------|------|
| 直接 DELETE | 簡單free space 由 autovacuum 回收 | **採用** |
| 移到 ai_calls_archive 表 | 多一份儲存,需另寫查詢 | 否 |
| 匯出 JSON 到 S3/GCS | 完整保留,可重建 | Phase 5 後若有合規需求再加 |
**理由**ai_calls 是遙測30 天前的單筆價值低trend 已在週報/月報沉澱到 ai_insights。
**清理任務**scheduler 每日 03:00
```sql
DELETE FROM ai_calls WHERE called_at < NOW() - INTERVAL '90 days';
```
配合 `idx_ai_calls_called_at DESC` 倒序掃描DELETE 範圍小(每日 ~72k不會 lock。
---
## Section 2 — 是否與既有 schema 衝突
### 2.1 與 `ai_generation_history`4 處 ai_routes.py
- 用途不同ai_generation_history 是 **產品功能側**is_favorite / is_used / created_by 都是業務欄位)
- ai_calls 是 **基礎設施側遙測**
- **共存策略**A4 接 logger 時ai_routes.py 那 4 處 **同時雙寫** 兩張表(既有 history 不破壞ai_calls 是 superset
### 2.2 與 `ai_usage_tracking`database/ai_models.py L72
- ai_usage_tracking 已存在但**完全沒被 30 個呼叫點接入**A1 audit 已驗證)
- 設計欄位service_type / request_type / user_id與 v5.0 戰役所需caller / provider / fallback_to / request_id不符
- **建議**A4 logger 統一寫 ai_callsai_usage_tracking **凍結**(不寫入但不刪表,避免 model import 鏈斷裂);待 Phase 5 報表驗證 ai_calls 完整後Phase 12 再 deprecate
### 2.3 與 `ai_insights.embedding_signature`
- 既有 ai_insights 表**沒有** embedding_signature 欄位(已驗證 `database/ai_models.py:111-151`
- 新增為 NULL**metadata-only ALTER TABLE**不鎖表PostgreSQL 11+ 安全)
- **無衝突**
---
## Section 3 — 三個查詢效能預估
模擬負載90 天滿載 ~6.5M 筆,索引 warm。
### 查詢 1過去 24h 某 caller 的 token 累計 + 成本Telegram 日報)
```sql
SELECT
caller,
SUM(input_tokens + output_tokens) AS total_tokens,
SUM(cost_usd) AS total_cost,
COUNT(*) AS call_count,
AVG(duration_ms) AS avg_latency
FROM ai_calls
WHERE called_at >= NOW() - INTERVAL '24 hours'
AND caller = 'openclaw_daily'
GROUP BY caller;
```
**預期執行計畫**
```
Aggregate
└─ Index Scan using idx_ai_calls_caller_called_at on ai_calls
Index Cond: (caller = 'openclaw_daily' AND called_at >= ...)
```
**預期 latency**< 5ms單 caller 24h ~144 筆,索引完全命中)
**鎖風險**:無,純 SELECT。
**OLTP 衝擊**:無。
### 查詢 2過去 7 天 by provider 統計(週報)
```sql
SELECT
provider,
COUNT(*) AS call_count,
SUM(input_tokens + output_tokens) AS total_tokens,
SUM(cost_usd) AS total_cost,
SUM(CASE WHEN status='error' THEN 1 ELSE 0 END) AS error_cnt,
SUM(CASE WHEN status='fallback' THEN 1 ELSE 0 END) AS fallback_cnt,
SUM(CASE WHEN cache_hit THEN 1 ELSE 0 END) AS cache_hits
FROM ai_calls
WHERE called_at >= NOW() - INTERVAL '7 days'
GROUP BY provider
ORDER BY total_cost DESC;
```
**預期執行計畫**
```
Sort
└─ HashAggregate
└─ Index Scan using idx_ai_calls_provider_called_at on ai_calls
Index Cond: (called_at >= ...)
```
**預期 latency**~50-150ms7 天 ~500k 筆6 個 provider HashAggregate
**鎖風險**:無。
**優化建議**:若 latency 退化到 > 200ms可加 covering index `(provider, called_at, input_tokens, output_tokens, cost_usd)` — V1 先不做。
### 查詢 3TOP 10 caller by token日報 Section 3
```sql
SELECT
caller,
SUM(input_tokens + output_tokens) AS total_tokens,
SUM(cost_usd) AS total_cost
FROM ai_calls
WHERE called_at >= NOW() - INTERVAL '24 hours'
GROUP BY caller
ORDER BY total_tokens DESC
LIMIT 10;
```
**預期執行計畫**
```
Limit
└─ Sort (top-N)
└─ HashAggregate
└─ Index Scan using idx_ai_calls_called_at on ai_calls
Index Cond: (called_at >= ...)
```
**預期 latency**~30-80ms24h ~72k 筆35 個 caller
**鎖風險**:無。
### 查詢效能總表
| 查詢 | 預期 latency | 主要索引 | 改善空間 |
|------|-------------|---------|---------|
| Q1 caller 24h | < 5ms | idx_ai_calls_caller_called_at | 已最佳 |
| Q2 provider 7d | 50-150ms | idx_ai_calls_provider_called_at | 可加 covering index |
| Q3 TOP-10 caller 24h | 30-80ms | idx_ai_calls_called_at | OK |
---
## Section 4 — 寫入吞吐評估
### 4.1 尖峰負載
- **峰值**50 inserts/min ≈ 0.83 ins/sec
- **單筆 insert 預估**5 個索引 × ~1ms WAL flush ≈ 3-8ms
- **目標**p99 < 50ms ✅(極大 buffer
### 4.2 風險點
| 風險 | 機率 | 影響 | 緩解 |
|-----|-----|------|-----|
| async fire-and-forget 失敗無告警 | 中 | log 漏寫 | logger 端用 try/except + 告警 channel連續 5 次失敗觸發 Telegram |
| 5 個索引導致寫入放大 | 低 | 同步寫入慢 | partial index 已縮減50 ins/min 下不會擠壓 OLTP |
| autovacuum 跟不上 90 天 DELETE | 低 | 表膨脹 | 每日 03:00 DELETE配 autovacuum_scale_factor=0.05 |
### 4.3 connection pool 衝擊
A4 logger 採 **fire-and-forget**(非同步寫,不阻塞 caller須使用獨立 thread + dedicated session pool建議 size=2與主應用 pool 隔離),避免擠壓 OLTP。
---
## Section 5 — 風險與限制
### 5.1 已知限制
1. **ai_calls 不分區V1**:月寫入超 5M 或日報 latency p95 > 500ms 時須升級到 monthly partition
2. **JSONB meta 無 GIN index**:未來若要 `WHERE meta->>'prompt_hash' = ...` 查詢,需另加 GIN
3. **保留策略硬刪除**30+ 天前的個別呼叫無法回溯trend 須先進 ai_insights
4. **ai_call_budgets.provider NULL 唯一性**靠部分索引強制PostgreSQL 標準 UNIQUE 不認 NULL
### 5.2 護欄缺口(待後續 phase 補)
- **Phase 5**ai_calls 寫入失敗的告警通道未定(建議走 EventRouter L0
- **Phase 9**ai_call_budgets 的 alert_pct 預設 80% 是否合理待實測budget 超標的 hard-stop 邏輯由應用層實作
- **Phase 11**embedding_signature 既有 ~XX 萬筆需批次回填(待 SSH 188 跑統計)
### 5.3 ALTER TABLE 026 安全性確認
- PostgreSQL 14momo-db 容器版本,待 SSH 確認)
- 11+ 之後 ADD COLUMN 無 DEFAULT 為 metadata-only**不鎖表,不重寫**
- CREATE INDEX CONCURRENTLY 不阻塞既有寫入,但**不能在 transaction 內**migration 026 註記已標明)
---
## Section 6 — 部署 Checklist給統帥
執行順序與檢查(憲法 ADR-008 — 部署前必驗):
- [ ] **SSH 188 確認 PostgreSQL 版本** ≥ 14migration 026 ALTER TABLE 安全前提)
- [ ] **SSH 188 確認 momo-db 磁碟剩餘空間** ≥ 5GB90 天滿載 ~3GB + headroom
- [ ] **SSH 188 確認 ai_insights 既有筆數**`SELECT COUNT(*), COUNT(embedding) FROM ai_insights;`(評估 Phase 11 回填工作量)
- [ ] 跑 024`psql -U momo -d momo_pro -f migrations/024_create_ai_calls_table.sql`
- [ ] 跑 025`psql -U momo -d momo_pro -f migrations/025_create_mcp_calls_and_budgets.sql`
- [ ] **跑 026 須注意**:含 `CREATE INDEX CONCURRENTLY`**不能用 BEGIN/COMMIT 包**;用 `psql -1` 會失敗,須用一般 `psql`
- [ ] 026 後驗證:`\d ai_insights` 看到 embedding_signature 欄位 + idx_ai_insights_embedding_signature 索引
- [ ] 跑 sanity`SELECT * FROM ai_call_budgets ORDER BY id;`(確認 5 筆種子)
- [ ] 通報 A4可開始接 logger
---
## Section 7 — Commit Message 草稿(不自動 commit
```
db: ai_calls/mcp_calls/budgets schema + bge-m3 signature (Operation Ollama-First v5.0 P1)
- migrations/024: ai_calls 統一 LLM 遙測表 (5 indexes, partial idx for sparse cols)
- migrations/025: mcp_calls + ai_call_budgets (Phase 10/9 預備, 含 5 筆種子預算)
- migrations/026: ai_insights.embedding_signature + partial index (BGE-M3 護欄)
- docs/phase1_db_design_20260503.md: 設計理由 + 查詢效能預估 + 部署 checklist
無 schema 衝突ai_usage_tracking 凍結待 Phase 12 deprecateA4 logger 可啟動。
依據: docs/phase0_audit_report_20260503.md (34 LLM 呼叫點 / 11.8% 覆蓋率)
```
---
## DB Expert Report最終結論
### 審查範圍
- 新增檔案:`migrations/024_create_ai_calls_table.sql``migrations/025_create_mcp_calls_and_budgets.sql``migrations/026_add_embedding_signature.sql`
- 影響資料表:`ai_calls`(新)、`mcp_calls`(新)、`ai_call_budgets`(新)、`ai_insights`ADD COLUMN
### 問題清單
無 BLOCKER。
#### 🟡 NOTE 1 — ai_usage_tracking 重疊
- 位置:`database/ai_models.py:72-109`
- 說明:既有但未被 30 個呼叫點使用,欄位語意不對齊。
- 風險A4 寫 logger 時若誤雙寫此表會造成數據混亂。
- 緩解:在設計文 Section 2.2 已明示「凍結Phase 12 再 deprecate」。
#### 🟡 NOTE 2 — Migration 026 不能用 BEGIN/COMMIT 包
- 位置:`migrations/026_add_embedding_signature.sql`
- 說明:`CREATE INDEX CONCURRENTLY` 不能在 transaction block 內執行。
- 緩解:已在檔頭註記,部署 checklist 已標明不用 `psql -1`
### 效能分析
- Q1 caller-24h< 5msidx_ai_calls_caller_called_at
- Q2 provider-7d50-150msidx_ai_calls_provider_called_at + HashAggregate
- Q3 TOP-10 caller30-80msidx_ai_calls_called_at + Top-N Sort
- 寫入3-8ms p50p99 < 50ms 達標
### 結論
**APPROVED WITH NOTES** — Schema 已備妥,無阻擋 A4 logger 啟動的問題。
### 回滾路徑
三份 migration 檔頭皆附完整回滾 SQL測試環境可一鍵 DROP。