Files
DashBoard/openspec/changes/archive/2026-03-02-unified-batch-query-redis-cache/proposal.md

84 lines
6.4 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.

## Why
目前各歷史報表服務reject-history、hold-history、resource-history、查詢工具query-tool、中段不良分析mid-section-defect和 Job 查詢job-query各自實作不同的批次查詢、快取和並行執行模式缺乏統一編排與保護。主要問題
1. **Oracle 超時**長日期範圍365+ 天)或大量 Container ID工單展開後可達數千筆的查詢可能超過 300 秒 call_timeout
2. **OOM 風險**reject/hold dataset cache 以 `limit: 999999999` 取回全部資料,無記憶體上限守衛
3. **保護分散**`EventFetcher` 已有 ID 分批 + 快取,但 reject/hold/resource dataset cache 仍各自維護查詢與快取策略
4. **重複程式碼**3 個 dataset cache 各自複製相同的 parquet-in-Redis 序列化邏輯
5. **ID 展開膨脹**:工單 resolve 後 container ID 可能大量擴張,缺乏跨服務一致的分批/合併流程
6. **重查成本高**:延長查詢範圍(例如 1-6 月改 1-8 月)無法有效重用已查區段結果
7. **query-tool 超時風險高**:多數查詢仍走 `read_sql_df`(主 pool / 55s timeout大查詢下容易超時
需要一個**可穩定複用的查詢引擎模組**,任何服務接入後自動獲得分解、快取、記憶體保護和超時保護。
## What Changes
- 新增 `BatchQueryEngine` 共用模組,提供:
- **時間範圍分解**:長日期 → ~31 天月份區間,每段獨立查詢
- **時間分解語意**:明確定義 chunk 邊界(閉區間)、跨月切割與最後一段不足月行為
- **ID 批次分解**:大量 ID工單/Lot/GD Lot/流水批展開後)→ 1000 筆一批
- **query_hash 規格**:統一 canonicalization 與雜湊欄位,確保 chunk/cache key 穩定
- **記憶體守衛**:每個 chunk 結果檢查 `DataFrame.memory_usage()`,超過閾值時中止並警告
- **結果筆數限制**:可配置的最大結果筆數,超過時截斷並標記
- **受控並行執行**:預設循序、可選並行,嚴格遵守 slow query semaphore
- **Redis 分塊快取**:每個 chunk 獨立快取,支援部分命中(延長查詢範圍時複用已查過的區間)
- **快取層互動**:明確定義 chunk cache 與服務既有 L1/L2 dataset cache 的讀寫順序
- **進度追蹤**Redis HSET 記錄進度,可供前端顯示
- 新增「**大結果落地層Parquet spill**」設計:
- 當長查詢結果超過記憶體/列數門檻時,將合併後結果以 Parquet 寫入本機持久目錄(例如 `tmp/query_spool/`
- Redis 僅保存 metadataquery_id → parquet path / schema / rows / created_at / ttl
- `/view``/export` 讀取流程優先走 Redis metadata + Parquet避免整包 DataFrame 常駐 worker RAM
- 定時清理TTL + 背景清理器)刪除過期 parquet避免磁碟持續膨脹
- 新增 `redis_df_store` 共用模組,將 parquet-in-Redis 存取邏輯從 3 個 dataset cache 提取為共用工具
- 所有**引擎接管的 chunk 查詢**統一使用 slow 路徑300 秒級 timeout
- 使用既有「**獨立 SLOW POOL小容量**」做慢查詢連線重用
- 明確**不使用主查詢 pool** 承載慢查詢,避免拖垮一般 API
- 當 SLOW POOL 不可用時,降級為 slow direct connection不影響主 pool
## Capabilities
### New Capabilities
- `batch-query-engine`: 統一批次查詢引擎模組,涵蓋分解策略(時間/ID、記憶體守衛、結果限制、受控執行、Redis 分塊快取、進度追蹤、結果合併
### Modified Capabilities
- `reject-history-api`: 主查詢改為透過引擎執行date_range 模式自動時間分解container 模式(工單/Lot/GD Lot 展開後)自動 ID 分批
- `hold-dataset-cache`: 主查詢改為透過引擎執行,長日期自動分解
- `resource-dataset-cache`: 主查詢改為透過引擎執行,長日期自動分解
- `event-fetcher-unified`: 保持既有最佳化batch + streaming + cache僅在需要統一監控/進度模型時再評估導入
## Impact
- **後端**:新增 2 個共用模組(`batch_query_engine.py``redis_df_store.py`),優先修改 3 個 dataset cache 主查詢路徑reject/hold/resource
- **受影響服務**(優先順序):
- P0reject-history最容易超時/OOM — 長日期 + 工單展開 + 目前 `limit=999999999`
- P1hold-history、resource-history相同架構直接套用
- P2mid-section-defect4 階段管線,偵測查詢 + 上游歷史、job-query缺快取 + 日期分解)
- P3query-tool優先處理 `read_sql_df` 高風險路徑並導入慢查詢保護、event-fetcher保持可選
- **資料庫**:不改 SQL僅縮小每次查詢的 bind parameter 範圍
- **資料庫連線策略**:慢查詢與一般 pooled query 隔離,避免資源互相干擾
- **Redis**:新增 `batch:*` 前綴的分塊快取鍵
- **儲存層**:新增 Parquet 結果落地目錄與清理機制Redis 轉為索引/metadata不再承載全部大結果
- **記憶體**:引擎強制單 chunk 記憶體上限(預設 256MB超過時中止
- **可用性**Redis 設定 `maxmemory` + eviction 後仍可透過 Parquet metadata 回復查詢結果cache 不命中不等於資料遺失)
- **向下相容**:短查詢(< 60 、< 1000 ID走現有路徑零額外開銷既有 route/event 快取策略保持不變
- **前端**可選性變更長查詢可顯示進度條非必要
## Parquet 落地的預期效果與副作用
**預期效果:**
- 大幅降低 worker merge + cache 回填階段的峰值記憶體避免單 worker 突增到 GB
- Redis 記憶體由存整包資料轉為存索引/熱資料」,降低 OOM lock timeout 連鎖風險
- 服務重啟後 parquet 尚未過期仍可恢復查詢結果搭配 metadata
**可能副作用Side Effects**
- 磁碟 I/O 增加查詢高峰時會有 parquet 寫入/讀取尖峰
- 磁碟容量風險清理策略失效時spool 目錄可能持續膨脹
- 資料一致性風險metadata 指向檔案若被外部刪除/損壞會出現 stale pointer
- 安全與治理落地檔案需納入權限控管備份/清理與稽核策略
**緩解方向:**
- 強制 TTL + 定期掃描清理 metadata 與檔案 mtime 雙重判斷
- 啟動時做 orphan/stale 檢查與自動修復 metadata 或刪孤兒檔
- 先以 reject-history 長查詢為 P0逐步擴展到其他服務