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>
14 KiB
Phase 1 DB Design — Operation Ollama-First v5.0
日期:2026-05-03 作者:A3 db-expert 產出:3 個 migration(024/025/026)+ 設計理由 + 效能評估 依據:
docs/phase0_audit_report_20260503.md34 個 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 也只到 ~10B,INT4 上限 21 億夠) |
duration_ms INT |
監控 LLM 慢查;可為 NULL(AiderHeal 走 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.000001(OpenRouter 細粒計費需要) | 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%+ 是 ok,partial 大幅縮體) |
未建立的索引:
meta JSONB的 GIN index — V1 不建。GIN 寫入放大 ~3-5x,且尚未確定查詢 pattern;Phase 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 data,DELETE 不 archive
| 選項 | 優劣 | 結論 |
|---|---|---|
| 直接 DELETE | 簡單,free space 由 autovacuum 回收 | 採用 |
| 移到 ai_calls_archive 表 | 多一份儲存,需另寫查詢 | 否 |
| 匯出 JSON 到 S3/GCS | 完整保留,可重建 | Phase 5 後若有合規需求再加 |
理由:ai_calls 是遙測,30 天前的單筆價值低;trend 已在週報/月報沉澱到 ai_insights。 清理任務(scheduler 每日 03:00):
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_calls,ai_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 日報)
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 統計(週報)
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-150ms(7 天 ~500k 筆,6 個 provider HashAggregate)
鎖風險:無。
優化建議:若 latency 退化到 > 200ms,可加 covering index (provider, called_at, input_tokens, output_tokens, cost_usd) — V1 先不做。
查詢 3:TOP 10 caller by token(日報 Section 3)
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-80ms(24h ~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 已知限制
- ai_calls 不分區(V1):月寫入超 5M 或日報 latency p95 > 500ms 時須升級到 monthly partition
- JSONB meta 無 GIN index:未來若要
WHERE meta->>'prompt_hash' = ...查詢,需另加 GIN - 保留策略硬刪除:30+ 天前的個別呼叫無法回溯(trend 須先進 ai_insights)
- 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 14(momo-db 容器版本,待 SSH 確認)
- 11+ 之後 ADD COLUMN 無 DEFAULT 為 metadata-only:不鎖表,不重寫
- CREATE INDEX CONCURRENTLY 不阻塞既有寫入,但不能在 transaction 內(migration 026 註記已標明)
Section 6 — 部署 Checklist(給統帥)
執行順序與檢查(憲法 ADR-008 — 部署前必驗):
- SSH 188 確認 PostgreSQL 版本 ≥ 14(migration 026 ALTER TABLE 安全前提)
- SSH 188 確認 momo-db 磁碟剩餘空間 ≥ 5GB(90 天滿載 ~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 deprecate;A4 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:< 5ms(idx_ai_calls_caller_called_at)
- Q2 provider-7d:50-150ms(idx_ai_calls_provider_called_at + HashAggregate)
- Q3 TOP-10 caller:30-80ms(idx_ai_calls_called_at + Top-N Sort)
- 寫入:3-8ms p50,p99 < 50ms 達標
結論
APPROVED WITH NOTES — Schema 已備妥,無阻擋 A4 logger 啟動的問題。
回滾路徑
三份 migration 檔頭皆附完整回滾 SQL;測試環境可一鍵 DROP。