6.4 KiB
6.4 KiB
Why
目前各歷史報表服務(reject-history、hold-history、resource-history)、查詢工具(query-tool)、中段不良分析(mid-section-defect)和 Job 查詢(job-query)各自實作不同的批次查詢、快取和並行執行模式,缺乏統一編排與保護。主要問題:
- Oracle 超時:長日期範圍(365+ 天)或大量 Container ID(工單展開後可達數千筆)的查詢可能超過 300 秒 call_timeout
- OOM 風險:reject/hold dataset cache 以
limit: 999999999取回全部資料,無記憶體上限守衛 - 保護分散:
EventFetcher已有 ID 分批 + 快取,但 reject/hold/resource dataset cache 仍各自維護查詢與快取策略 - 重複程式碼:3 個 dataset cache 各自複製相同的 parquet-in-Redis 序列化邏輯
- ID 展開膨脹:工單 resolve 後 container ID 可能大量擴張,缺乏跨服務一致的分批/合併流程
- 重查成本高:延長查詢範圍(例如 1-6 月改 1-8 月)無法有效重用已查區段結果
- 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 僅保存 metadata(query_id → parquet path / schema / rows / created_at / ttl)
/view與/export讀取流程優先走 Redis metadata + Parquet,避免整包 DataFrame 常駐 worker RAM- 定時清理(TTL + 背景清理器)刪除過期 parquet,避免磁碟持續膨脹
- 當長查詢結果超過記憶體/列數門檻時,將合併後結果以 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) - 受影響服務(優先順序):
- P0:reject-history(最容易超時/OOM — 長日期 + 工單展開 + 目前
limit=999999999) - P1:hold-history、resource-history(相同架構,直接套用)
- P2:mid-section-defect(4 階段管線,偵測查詢 + 上游歷史)、job-query(缺快取 + 日期分解)
- P3:query-tool(優先處理
read_sql_df高風險路徑並導入慢查詢保護)、event-fetcher(保持可選)
- P0:reject-history(最容易超時/OOM — 長日期 + 工單展開 + 目前
- 資料庫:不改 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,逐步擴展到其他服務