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

6.4 KiB
Raw Blame History

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.pyredis_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逐步擴展到其他服務