工業控制系統有一個長期存在的矛盾:現場硬體往往極度穩定, 但上層軟體架構卻累積了大量的歷史包袱與安全死角。 DOF Lab 的 Z72 SCADA 系統監控 14 台 Bachmann M1 風機, 在完成 Phase 3a(振動趨勢、CSV 匯出、單元測試套件)之後, 現在面臨兩個重要的技術節點: 一是架構設計層面的 Phase 3b ADR——選擇後端語言與框架, 二是一個實際影響時序資料完整性的細緻 Bug, 即 Historian 啟動時 high-water mark 初始化錯誤導致第 15 天資料遺失。 這兩件事雖然看似無關,實際上共同反映了一個命題: 工業 AI 研究平台的可信度,最終取決於你在最不顯眼的地方投入了多少工程嚴謹度。
一、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 的核心理由有三:
- Spring Boot 3 需要 Java 17+,而現有 bridge 為了相容 Bachmann 環境仍在 Java 8, 這會在同一台工業電腦上產生雙 JVM 版本共存的維護噩夢。
- Phase 3b 的需求本質上是 I/O 密集型代理(WebSocket proxy + JWT + SQLite), FastAPI 的 asyncio 架構在這個場景下表現優異,且開發迭代速度遠快於 Spring Boot。
- Python 生態系統為未來的預測性維護功能鋪路——當 Z72 SCADA 積累了足夠的歷史資料, 直接在 Python backend 呼叫 scikit-learn 或接入 windMindOM 的 RAG 知識庫, 技術障礙遠低於在 JVM 上整合 Python ML 工具。
💡 工業 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 重啟後,oneMinHighWaterMarkMs 與 tenMinHighWaterMarkMs 是從「當下時間往回推 retention 時間」計算的, 而不是從「資料庫中實際最舊的 raw 紀錄時間戳」計算的。
⚠️ Bug 根因:high-water mark 初始化沒有錨定實際資料
原始邏輯:highWaterMark = (now - retentionMs) / windowMs * windowMs
問題:如果 bridge 在 Day 15 重啟,now - retentionMs 計算出的起點
比資料庫中最舊的 raw 紀錄還要新。那些介於「最舊 raw 時間戳」與「計算起點」之間的資料,
永遠不會被 rollup——直到 raw 保留期到期被刪除,rollup 窗格就憑空消失了。
在長時間連續運行的生產環境,這代表歷史趨勢圖會出現間隙,甚至整段的資料遺失。
修復邏輯很優雅:
- 在 Historian.java 初始化時,先查詢 SELECT MIN(ts) FROM history_raw,取得資料庫中最早的原始時間戳。
- 如果 oldestRawTs > 0(資料庫非空), 以 Math.max(oldestRawTs, now - retentionMs - 1day) 作為 high-water mark 起點, 確保 rollup 從真實資料起點出發,而不是從「理論上應該有資料」的時間點出發。
- 同時為這個修復補上單元測試,驗證啟動後 high-water mark 與 DB 中最舊資料的一致性。
這個 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 會告訴你當初考慮了什麼、排除了什麼、還有哪些問題沒有解決—— 這比任何口頭傳承都更可靠。