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>
4.0 KiB
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
D1:SQLite 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 語句自然整合。
D2:RSS 記憶體取得方式
選擇:resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * 1024(Python stdlib,Linux 上單位為 KB)
替代方案 A:讀取 /proc/self/status VmRSS → 平台相依,解析 overhead
替代方案 B:psutil.Process().memory_info().rss → 需新增外部依賴
理由:resource 模組為 Python 標準庫,無需額外依賴。ru_maxrss 在 Linux 上返回 KB,乘以 1024 轉為 bytes。
D3:Semaphore 排隊計數器實作
選擇:在 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 保護。
D4:Archive log cleanup 整合位置
選擇:整合到 MetricsHistoryCollector._run() 的 cleanup cycle(每 ~100 intervals ≈ 50 分鐘)
替代方案:獨立 cron job → 需額外 crontab 配置,不自包含
理由:已有 daemon thread 定期 cleanup SQLite,加入 archive cleanup 邏輯一致且自包含。
D5:移除 Jinja fallback 的安全性
選擇:直接移除 fallback,admin_routes.py 改為只 send_from_directory(dist_dir, "admin-performance.html")
理由:
- Vue SPA build artifact 已存在(
static/dist/admin-performance.html,2026-02-26 更新) frontend/package.jsonbuild 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 rows,ALTER 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。