Address 6 code review findings (P0-P3): add Redis distributed lock to prevent duplicate Oracle pipeline on cold cache, apply rate limiting to 3 high-cost routes, separate UI filter state from committed query state, add AbortController for request cancellation, push workcenter group classification into Oracle SQL CASE WHEN, and add 18 route+service tests. Also add workcenter group selection to job-query equipment selector and rename button to "查詢". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.4 KiB
Context
/mid-section-defect 頁面已上線,功能完整但缺乏生產環境保護機制。Code review 揭示 6 個問題,依嚴重度分為 P0-P3。現有基礎建設(try_acquire_lock、configured_rate_limit、createAbortSignal)均可直接複用,無需新增框架。
現有架構:
- Backend:
query_analysis()(line 82-184) 含 5min Redis 快取 →query_analysis_detail()(line 187-224) 呼叫前者取快取結果再分頁 - Frontend:
Promise.all([summary, detail])平行載入 →useAutoRefresh5min 自動刷新 - 上游歷史: SQL 查全量 → Python
get_workcenter_group()逐行分類 + order 4-11 過濾
Goals / Non-Goals
Goals:
- 消除首次查詢的雙倍 Oracle 管線執行(P0)
- 保護高成本路由免受暴衝流量(P1a)
- 確保 UI 篩選變更不會汙染進行中的 API 呼叫(P1b)
- 新查詢自動取消舊的進行中請求(P2a)
- 善用 Oracle Server 做 workcenter 分類,支援全線歷程追蹤(P2b)
- 基礎測試覆蓋防止回歸(P3)
Non-Goals:
- 不改變 API response 格式
- 不重構
query_analysis()管線邏輯 - 不加入前端 UI 新功能
- 不處理
export_csv()的串流效能(目前可接受) - 不做 DuckDB 中間層或背景預計算
Decisions
D1: 分散式鎖策略 — Redis SET NX 輪詢等待
選擇: 使用既有 try_acquire_lock() + 輪詢 cache_get() 等待模式。
替代方案: (A) Pub/Sub 通知 — 複雜度高,需新增 channel 管理;(B) 前端序列化 — 改 Promise.all 為先 summary 再 detail,但仍有自動刷新與手動查詢並行問題。
理由: 鎖機制在 service 層統一保護所有入口(包含未來新路由),fail-open 設計確保 Redis 故障不阻塞。輪詢 0.5s 間隔在 5-35s 典型管線執行時間下損耗可忽略。
D2: Rate limit 預設值 — 依路由成本分級
選擇: /analysis 6/60s、/detail 15/60s、/export 3/60s。
理由: /analysis 冷查詢 35s,每分鐘最多 6 次已足夠(含自動刷新)。/detail 分頁翻頁頻率高但走快取,15 次寬裕。/export 觸發全量串流,3 次防誤操作。/loss-reasons 已有 24h 快取,無需限速。
D3: 篩選分離 — committedFilters ref 快照
選擇: 新增 committedFilters ref,按「查詢」時從 filters reactive 快照。所有 API 函式讀 committedFilters。
替代方案: (A) deep watch + debounce — 會在使用者輸入中途觸發查詢;(B) URL params 持久化 — 此頁面不需要書籤分享功能。
理由: 最小改動,與 resource-history/App.vue 的 buildQueryString() 模式一致。filters reactive 繼續作為 UI 雙向繫結,committedFilters 是「上次查詢使用的參數」。
D4: AbortController — keyed signal 設計
選擇: 'msd-analysis' key 用於查詢(summary + detail page 1 共用),'msd-detail' key 用於獨立翻頁。
理由: 新查詢取消舊查詢的所有請求(含翻頁中的 detail),翻頁取消前一次翻頁但不影響進行中的查詢。與 wip-detail/App.vue 相同模式。
D5: 上游歷史 SQL 端分類 — CASE WHEN 全線保留
選擇: SQL CTE 內加 CASE WHEN 將 WORKCENTERNAME 分類為 WORKCENTER_GROUP(12 組 + NULL fallback),Python 端直接讀取分類結果,不過濾任何站點。
替代方案: (A) Oracle 自訂函式 — 需 DBA 部署;(B) 維持 Python 端分類但移除過濾 — 仍有 10K+ 行逐行 regex 開銷。
理由: CASE WHEN 在 Oracle 查詢引擎內原生執行,無 row-by-row function call 開銷。分類邏輯與 workcenter_groups.py 的 patterns 完全對齊,但需注意 CASE 順序(exclude-first: 元件切割 在 切割 之前)。
Risks / Trade-offs
- [P0 鎖等待超時] 若管線執行 >90s(極大日期範圍),等待方可能超時後自行查詢 → 緩解:API_TIMEOUT 本身 120s,鎖 TTL 120s 會自動釋放,最壞情況退化為當前行為(雙查詢)
- [P2b SQL 分類與 Python 不一致] 若
workcenter_groups.py新增/修改 pattern 但忘記同步 SQL → 緩解:SQL 的 NULL fallback 確保不會遺失行,僅分類名稱可能為 NULL - [Rate limit 誤擋] 高頻翻頁或自動刷新可能觸發限速 → 緩解:
/detail15/60s 已足夠正常翻頁(每 4s 一頁),自動刷新 5min 間隔遠低於/analysis6/60s 門檻