承接昨天的蘿蔔蹲。接下來要打造的是一套工作流程管理系統。不過在寫第一行程式之前,想先講清楚一件事:
為什麼這個專案是 2.0,而不是把 1.0 再講一遍?
答案已經寫在標題上。1.0 是用 Django 做後端、走集中式管理的 Flow Tracer;2.0 則把鏡頭轉回每一位 APR Block Owner 自己的桌上。
1.0 不是本系列的主角,所以不會著墨太多。但架構抉擇如果只講結論、不講痛過什麼,讀起來會像空口說「這次比較輕量」。文章結尾會附上 1.0 的 Repo,有興趣可以自己翻。
| 項目 | 技術 |
|---|---|
| 後端框架 | Django 6.0.1 |
| 語言 | Python 3 |
| 資料庫 | SQLite(兩個 DB:default + main) |
| 訊息暫存 | Redis(自訂埠 8888) |
| 前端 | Bootstrap 5、Bootstrap Icons |
| Flow 視覺化 | Mermaid 10、svg-pan-zoom |
看起來就是一套「有網頁、有資料庫、有訊息佇列」的標準後台。當時的目標也很明確:讓很多人在同一個入口提交、同一個螢幕監看。
WinFlow 的運作主要分成 5 個部分:主網頁、任務執行檔生成器、Redis、db 寫入器、資料庫 (sqlite3)
┌─────────────────────────────────────────────────────────────┐
│ Web(Django) │
│ /create/ 建立任務 → Task.start() │
│ /monitor/ 任務總覽 │
│ /task/<uuid>/ DAG 詳情(Mermaid) │
└───────────────────────┬─────────────────────────────────────┘
│ 產生並執行
▼
┌─────────────────────────────────────────────────────────────┐
│ static/run_script/flow_start/start_<uuid>.sh │
│ ↓ │
│ static/tool/flow_start.py │
│ · 依 purpose 建立工作目錄與 flow_tree.json │
│ · RPUSH 到 Redis db=1,key=start_flow │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Redis(預設 127.0.0.1:8888) │
│ db=1 start_flow 新流程定義 │
│ db=2 stage_update Stage 狀態更新(預留) │
│ db=3 flow_update Task 狀態更新(預留) │
└───────────────────────┬─────────────────────────────────────┘
│ 輪詢消費
▼
┌─────────────────────────────────────────────────────────────┐
│ manage.py db_operator --interval N │
│ · 讀取 start_flow → 建立 Stage、寫入 Task.topology │
│ · stage_update / flow_update 介面已預留 │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SQLite │
│ db/db.sqlite3 Django 預設(auth、sessions…) │
│ db/main.sqlite3 資料(Task、Stage) │
└─────────────────────────────────────────────────────────────┘
使用流程可以收成六步:
Task.start() 啟動flow_tree.json,並推入 Redis start_flow
/monitor/ 與 /task/<uuid>/ 看總覽跟 DAG其中比較大的坑,出在「大量狀態更新」。當時可能同時有數百到數千筆狀態要寫,我一開始沒把 Redis 當 cache,而是直接往 SQLite 硬灌。
結果當然是悲劇——Flow 實際跑完的進度,比網頁顯示的狀態快了整整八個小時 😂
後來的解法很土、也很有效:更新先暫存在 Redis,再由定時批次寫進 SQLite。瓶頸緩過來了,架構也更咬死「Web 不直接扛寫入」。
相信你已經發現:1.0 不是讓使用者把工作直接交給 LSF,而是先統一交給「啟動 Server 的那個帳號」,再由該帳號把 Job 送進 LSF。
理論上這沒問題,反正 Job 有出去、有跑完就好(笑)。實務上,APR Block Owner 的權責劃分非常清楚——每個人要全權負責自己的 Block。所有工作如果都從同一隻公用帳號飛出去:
bjobs 上看不出這是誰的 place、誰的 route於是這套機制在團隊裡很難推。不是功能不夠,是帳號模型跟組織模型對不上。
1.0 的另一個設計初衷,是同時提交、同時監看「上百個 Job」。它最大的價值,是讓多人共用一個入口,代價只是佔用一台機器當 Server。可是一旦團隊當下沒有大量發送 Job 的需求,這個優勢會立刻變薄,露出另外兩條短板:
因此 2.0 換了一個鏡頭:以每一位 APR Block Owner 的個人視角與需求重新規劃。
不是否定集中管理。是承認:集中管理要養得起一台 Server、一套帳號政策、一個願意當管理員的人。養不起的時候,工具應該縮小到「我的桌面、我的帳號、我的 flow」。
| 項目 | 技術 |
|---|---|
| 語言 | Python 3(標準函式庫為主,不靠 pip 套件) |
| GUI | Tkinter 8.6 這一系(常見講法是 8.x;重點是系統自帶) |
沒有 Django,沒有 Redis,沒有 Mermaid 前端。狀態不進資料庫,進的是你眼前這份 JSON,以及 LSF 自己的 bjobs。
2.0 的運作主要就兩塊:使用者互動的 GUI,以及一份描述 Flow 的 JSON。站點預設另外放在 config.json。
+-------------+
| config.json |
+-------------+
|
v
+-------------+
| WinFlow2.0 | ---------------------------------
+-------------+ | |
| | |
v v v
+-------------+ +-----------+ +----------------+
| Flow Runner | <--- | flow.json | <--- | Flow Generator |
+-------------+ +-----------+ +----------------+
使用流程可以更短:
跟 1.0 比,少掉的不是「功能清單上的一項」,是整層中介:
責任歸屬因此變清楚。bjobs 上看到的 user,就是這個 block 的 owner。比較不會產生「這是誰丟的、該誰重跑、該誰負責佔 queue」的爭議。
當然也有代價:2.0 不打算當全公司的監控牆。它選擇把「誰在跑什麼」還給 LSF 跟當事人。這是刻意的取捨,不是還沒做完。
flow.json