Files
DashBoard/openspec/changes/archive/2026-02-26-admin-perf-vue-migration-monitoring-gaps/design.md
egg 07ced80fb0 feat(admin-perf): full Vue SPA migration + slow-query/memory monitoring gaps
Remove Jinja2 template fallback (1249 lines) — /admin/performance now serves
Vue SPA exclusively via send_from_directory.

Backend:
- Add _SLOW_QUERY_WAITING counter with get_slow_query_waiting_count()
- Record slow-path latency in read_sql_df_slow/iter via record_query_latency()
- Extend metrics_history schema with slow_query_active, slow_query_waiting,
  worker_rss_bytes columns + ALTER TABLE migration for existing DBs
- Add cleanup_archive_logs() with configurable ARCHIVE_LOG_DIR/KEEP_COUNT
- Integrate archive cleanup into MetricsHistoryCollector 50-min cycle

Frontend:
- Add slow_query_active and slow_query_waiting StatCards to connection pool
- Add slow_query_active trend line to pool trend chart
- Add Worker memory (RSS MB) trend chart with preprocessing
- Update modernization gate check path to frontend style.css

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:48:54 +08:00

82 lines
4.0 KiB
Markdown
Raw 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.

## Context
2026-02-25 的 server crash 暴露出 pool 隔離架構變更後的監控盲區。event_fetcher 和 lineage_engine 已遷移到 `read_sql_df_slow`(獨立連線 + semaphore但 metrics_history 快照只記錄 pool 相關指標slow query 並行數、排隊數、Worker RSS 完全無歷史紀錄。
同時 `/admin/performance` 仍保留 1249 行 Jinja template 作為 Vue SPA fallback但 SPA 已是唯一使用的版本build artifact 存在於 `static/dist/admin-performance.html`),兩套 UI 增加維護成本且 Jinja 版功能遠不及 SPA。
`logs/archive/` 目錄累積 rotated log 檔案無自動清理,是唯一會無限增長的儲存。
## Goals / Non-Goals
**Goals:**
- 移除 Jinja fallback統一為 Vue SPA 單一架構
- 讓 slow query 並行數、排隊數、Worker RSS 成為可觀測的歷史趨勢指標
- 讓 P50/P95/P99 反映所有查詢路徑pool + slow path
- 解決 archive log 無限增長問題
**Non-Goals:**
- 不修改 `/admin/pages`(仍為 Jinja template
- 不新增 async job queue 面板P1後續 change 處理)
- 不新增 event cache hit/miss 計數器P2
- 不增加即時告警或 webhook 通知機制
## Decisions
### D1SQLite schema migration 策略
**選擇**:啟動時執行 `ALTER TABLE ADD COLUMN IF NOT EXISTS`(容錯 "duplicate column" error
**替代方案**version table + migration script → 過度工程SQLite 只有 3 天保留,加欄是向後相容的
**理由**:新欄位 nullable舊 row 自動為 NULL不影響既有查詢。MetricsHistoryStore.initialize() 已在啟動時執行 CREATE TABLE IF NOT EXISTS加入 ALTER TABLE 語句自然整合。
### D2RSS 記憶體取得方式
**選擇**`resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * 1024`Python stdlibLinux 上單位為 KB
**替代方案 A**:讀取 `/proc/self/status` VmRSS → 平台相依,解析 overhead
**替代方案 B**`psutil.Process().memory_info().rss` → 需新增外部依賴
**理由**`resource` 模組為 Python 標準庫,無需額外依賴。`ru_maxrss` 在 Linux 上返回 KB乘以 1024 轉為 bytes。
### D3Semaphore 排隊計數器實作
**選擇**:在 `read_sql_df_slow()` 的 semaphore.acquire() 前後遞增/遞減 `_SLOW_QUERY_WAITING` atomic counter
**流程**
```
_SLOW_QUERY_WAITING += 1
acquired = semaphore.acquire(timeout=60)
_SLOW_QUERY_WAITING -= 1
if not acquired: raise RuntimeError
_SLOW_QUERY_ACTIVE += 1
... execute query ...
_SLOW_QUERY_ACTIVE -= 1
```
**理由**:與既有 `_SLOW_QUERY_ACTIVE` 模式一致,使用 threading.Lock 保護。
### D4Archive log cleanup 整合位置
**選擇**:整合到 `MetricsHistoryCollector._run()` 的 cleanup cycle每 ~100 intervals ≈ 50 分鐘)
**替代方案**:獨立 cron job → 需額外 crontab 配置,不自包含
**理由**:已有 daemon thread 定期 cleanup SQLite加入 archive cleanup 邏輯一致且自包含。
### D5移除 Jinja fallback 的安全性
**選擇**:直接移除 fallbackadmin_routes.py 改為只 `send_from_directory(dist_dir, "admin-performance.html")`
**理由**
- Vue SPA build artifact 已存在(`static/dist/admin-performance.html`2026-02-26 更新)
- `frontend/package.json` build script 已包含 admin-performance entry
- CI/deploy 流程必包含 `npx vite build`
- 若 build 失敗,`/health/frontend-shell` 已有 asset readiness 檢查可偵測
## Risks / Trade-offs
- **[Risk] Build 失敗時 /admin/performance 返回 404** → 既有 `/health/frontend-shell` 檢查 + deploy script 驗證。移除 fallback 反而讓問題更早暴露。
- **[Risk] ALTER TABLE 在 SQLite 大表上可能慢** → metrics_history 最多 50K rowsALTER TABLE 即時完成。
- **[Trade-off] `ru_maxrss` 是 peak RSS非 current RSS** → 在 Linux 上 `ru_maxrss` 是 process lifetime 的 max RSS。改用 `/proc/self/status` 的 VmRSS 可取得 current但需 file I/O。鑑於每 30 秒收集一次且 max RSS 更能反映記憶體壓力,接受此 trade-off。若日後需要 current RSS可改讀 `/proc/self/status`