← 返回研究日誌

Z72 SCADA Phase 3b:選擇 FastAPI 作為工業後端,以及 Historian 第 15 天啟動資料遺失修復

2026-05-28 • 劉瑞弘教授團隊 • Industrial SCADA / Architecture Decision / Java Bridge / Time-Series Historian

ADR 架構決策 FastAPI vs Spring Boot Historian 修復 Z72 SCADA Bachmann M1 / OPC UA

工業控制系統有一個長期存在的矛盾:現場硬體往往極度穩定, 但上層軟體架構卻累積了大量的歷史包袱與安全死角。 DOF Lab 的 Z72 SCADA 系統監控 14 台 Bachmann M1 風機, 在完成 Phase 3a(振動趨勢、CSV 匯出、單元測試套件)之後, 現在面臨兩個重要的技術節點: 一是架構設計層面的 Phase 3b ADR——選擇後端語言與框架, 二是一個實際影響時序資料完整性的細緻 Bug, 即 Historian 啟動時 high-water mark 初始化錯誤導致第 15 天資料遺失。 這兩件事雖然看似無關,實際上共同反映了一個命題: 工業 AI 研究平台的可信度,最終取決於你在最不顯眼的地方投入了多少工程嚴謹度

14
台 Bachmann M1 風機
93%
Z72 SCADA 完成度
ADR 0002
Phase 3b 架構決策
~10 GB
SQLite historian 資料量

一、Phase 3b ADR:為什麼選 Python FastAPI 而非 Java Spring Boot?

Z72 SCADA 的 Phase 3a 架構是純 client-side 設計: 瀏覽器直接透過 WebSocket 連接到 Java z72bridge, 存取 Bachmann M1 PLC 的即時資料與歷史查詢。這個設計的優點是低延遲、簡單, 但它的代價也很明顯——所有憑證(USERS 陣列)都硬編碼在 HTML/JS 原始碼裡, 沒有 JWT、沒有稽核軌跡、沒有角色權限控管

Phase 3b 的目標是引入一個真正的 SCADA 後端(z72scada-backend, 作為安全閘道器,處理 JWT 認證、SQLite 使用者管理、LDAP/AD 整合, 並代理 HMI 到 bridge 的 WebSocket 連線。 核心問題是:這個後端要用 Java + Spring Boot 3,還是 Python + FastAPI?

ADR 0002 決策框架

ADR(Architecture Decision Record)是一種輕量級的架構文件格式, 用來記錄「在什麼情境下、考慮了哪些選項、最終做了什麼決定、以及為什麼」。 DOF Lab 採用 ADR 的目的是確保未來的技術債清理或架構演進都有清晰的決策脈絡, 而不是讓後繼開發者面對一堆不知道為何存在的技術選擇。

乍看之下,Java 是更自然的選擇:bridge 本身就是 Java,PLC 廠商函式庫(mjsys.jar)是 Java,保持語言統一有其邏輯。但 ADR 0002 分析後,給出了推薦 FastAPI 的幾個關鍵理由:

評估維度 Java + Spring Boot 3 Python + FastAPI
開發速度 慢(大量樣板程式碼) 快(Pydantic 自動驗證、OpenAPI 自動文件)
記憶體佔用(閒置) 150–250 MB(JVM 開銷) 30–50 MB(Uvicorn + asyncio)
語言一致性 高(全 JVM 環境) 低(Java Bridge + Python Backend 混合)
執行環境需求 需要 Java 17+(bridge 用 Java 8,需雙版本) Python 3.10+(獨立安裝,不干擾 bridge)
AI/資料科學生態 有限 豐富(pandas、scikit-learn、RAG 整合)
安全認證 Spring Security(成熟) PyJWT + passlib(輕量、足夠)

最終推薦 FastAPI 的核心理由有三:

💡 工業 AI 架構啟示:語言一致性 vs 生態彈性

ADR 0002 揭示了工業控制系統現代化的一個典型張力: 底層 PLC 通訊層需要穩定、相容、確定性強的語言(Java/C), 但上層分析與認證層需要開發速度快、生態豐富的語言(Python)。 一刀切地堅持「全棧統一語言」有時候會為了理想的架構純粹性, 犧牲掉實際研究與產品開發所需的靈活性。 這個決策本身,就是工業 AI 領域中「系統整合現實主義」的典型體現。

二、Historian 第 15 天資料遺失:一個 high-water mark 初始化的邊界 Bug

Z72 Historian 採用三層時序資料架構: raw 14 天(每個 PLC 讀取點完整保留)→ 1min rollup 7 天10min rollup 永久保留。 這個設計的核心假設是:rollup 會把較舊的 raw 資料即時彙總到更低頻率的層, 確保歷史查詢不論時間跨距都有資料可用。

但有一個問題在 Day 15(系統運行第 15 天)悄悄浮現: 當 bridge 重啟後,oneMinHighWaterMarkMstenMinHighWaterMarkMs 是從「當下時間往回推 retention 時間」計算的, 而不是從「資料庫中實際最舊的 raw 紀錄時間戳」計算的

⚠️ Bug 根因:high-water mark 初始化沒有錨定實際資料

原始邏輯:highWaterMark = (now - retentionMs) / windowMs * windowMs

問題:如果 bridge 在 Day 15 重啟,now - retentionMs 計算出的起點 比資料庫中最舊的 raw 紀錄還要新。那些介於「最舊 raw 時間戳」與「計算起點」之間的資料, 永遠不會被 rollup——直到 raw 保留期到期被刪除,rollup 窗格就憑空消失了。 在長時間連續運行的生產環境,這代表歷史趨勢圖會出現間隙,甚至整段的資料遺失。

修復邏輯很優雅:

這個 Bug 的可怕之處在於它是時間依賴性的(time-dependent)—— 在系統運行 14 天之內看不出問題(raw 資料還在), 只有到第 15 天 raw 保留期開始輪轉時才會觸發資料遺失。 這類 Bug 在一般整合測試環境中很難被發現, 因為測試通常不會等待 14 天讓 retention 輪轉自然觸發。

時序資料系統的測試挑戰

這個修復案例展示了時序歷史系統在測試設計上的獨特挑戰: 許多關鍵 Bug 是「時間驅動」的,需要在測試中模擬時間加速或直接注入帶有偏移時間戳的測試資料, 才能在不等待真實時間流逝的情況下驗證邊界行為。 Z72 bridge 的單元測試套件(TestBridgeComponents.java) 已經更新為包含這類邊界情況的驗證, 作為 make_release.bat 打包前的強制關卡。

研究室行動項目與工程啟示

✅ 下一步:ADR 0002 決策確認與 Phase 3b 啟動

ADR 0002 目前狀態為「Proposed(等待決策)」, 需要劉老師確認以下三個開放問題:

  • 客戶端(運維廠商)的 IT 部門對允許安裝 Python runtime 是否有限制?
  • 研究室開發團隊在 Spring Boot vs FastAPI 哪邊有更強的實作能力?
  • 預測性維護 AI 功能(anomaly detection、RAG 整合)是否在 Phase 3b 之後的近期計畫內?

確認後,Phase 3b 的第一步是建立 z72scada-backend/ 專案骨架, 設定 SQLite 使用者資料表,並實作 /login JWT 端點。

綜合來看,這兩個工程事件共同傳遞了一個訊息: 工業 AI 系統的品質提升,從來不只是加新功能—— 更多時候是在「確保既有系統在極端邊界條件下也能正確運作」。 Historian 的 Day-15 Bug 沒有被漂亮的功能遮蓋住,而是被一個測試覆蓋到的原因, 正是因為研究室選擇了「先建立可信賴的基礎設施,再向前推進架構」的路線。 這個路線既是 Phase 3b ADR 的精神,也是整個 Z72 SCADA 系統開發哲學的縮影。

對於有意將工業資料整合進 AI 研究的團隊, ADR 0002 還提供了一個實用的提醒: 架構決策文件的價值不在於它有多正確,而在於它把「為什麼這樣選」記錄下來。 當六個月後你需要重新評估是否要引入 TimescaleDB、是否要把 Python backend 容器化部署到 GCP Cloud Run, 你的 ADR 會告訴你當初考慮了什麼、排除了什麼、還有哪些問題沒有解決—— 這比任何口頭傳承都更可靠。

參考來源