跳至主要内容

ADR-008: 效能壓測工具選型與 NFR 驗證定位

ADR 編號: 008 狀態: 已接受 (Accepted) 決策日期: 2026-07-14 決策者: AppFuse Team + AI Assistant 範圍: docs-methodology(效能壓測的工具選型、模組化與兩軌落點)

:::note 後續修訂(2026-07-16)

本 ADR 的工具選型結論(Locust)與三面向觀測、SLA 門檻、兩軌落點皆不變,但 module type 已更名:

本 ADR 原文現行
module type perf{app}-perfsim{app}-sim
m-perf-common.mdm-sim-common.md
perf_base.pysim_base.pyperf_monitor.py / perf_report.py 保留原名——量測裝置)
/perf-plan/sim-plan

原因:本 ADR 把「效能壓測」當成模組的本質,但它其實只是一種用途。模組的核心是一套使用者族群行為模擬器(操作情境 × 用戶分布 × 用戶行為);尖峰時間只是時間軸的一種參數化。同一組模型換時間軸(正常/縮時)並關掉量測裝置,即用於產生測試/展示資料長期營運模擬——後者另需 appfuse-server 提供參考時鐘。程式佈局、共用函數與設計觀念的比重,模擬遠高於量測,故屬名為 sim

權威定義見 m-sim-common.md「模擬器的兩種用途」。本 ADR 以下內容保留原始決策脈絡,未逐字改寫。

:::


摘要

下游專案有效能壓測需求,且框架家族內已有跨多個功能擴充週期、生產驗證過的 Locust 壓測實務(某科技教育網平台),但這套實務停在個案手工執行、未抽成框架慣例:每個下游各自重造、無 re-sync 管道。同時,效能壓測是跨切面 NFR 驗證,方法論原本以功能為單位的兩條軌(UI/Headless)與三階段驗證沒有替它留位置。

本 ADR 決定:①壓測工具採 Locust(Python);②壓測成為 workspace 的新 module type perf,比照 {app}-server{app}-sim;③三面向資源觀測(controller/monitor/sut)為壓測的觀測不變量;④壓測是 Server/對外契約 surface 的里程碑手動 NFR 閘門——計畫定 SLA 門檻、報告對照,不進三階段循環、不掛 CI gate。落地產物為 Tier 2 common practice m-sim-common.md


背景 (Context)

問題陳述

  1. 有成熟實務、無框架慣例:某科技教育網平台三個功能擴充週期(2023 / 2024 / 2025)各留一份完整壓測,骨架趨於一致——seed 驅動真實用戶池、加權情境、擬真時間、報名→取消的清理流、controller/monitor/sut 三面向觀測、計畫→報告生命週期。但它是一支手工維護的 techpro-perf Locust 模組,框架未定義任何對應的 module type、rule 或 scaffold 路徑。下游要壓測只能重造。

  2. NFR 在兩軌無位置:方法論的 UI 軌(Prototype→Server→Integration)與 Headless 軌都以功能(US)為單位推進、驗的是功能正確性。效能壓測驗的是「整個對外契約 surface 在真實負載下撐不撐得住」——per 里程碑、跨多端點、量延遲/吞吐/資源,與「per-US 功能驗證」正交,原模型容不下。

核心張力

Locust 那套實務用到大量非標準客戶端行為:從 9.5MB 真實學生池取樣、SSO 換 token 建 session、依欄位 metadata 動態填表、RSQL 查詢、報名後清理。這些正是「壓測工具 runtime 可擴充性」的試金石——用任意 Python 庫直接化解,換到沙箱化 JS runtime 的工具則須自寫擴充。工具選型不能只看「壓測引擎誰快」,要看「遇到超出內建模組的需求時誰擋路」。


決策 (Decision)

D1:壓測工具採 Locust(Python 家族)

壓測情境常需非標準客戶端行為(seed 驅動、動態組請求、重用既有 SDK、自訂計時與清理)。Locust 的任意 Python 可擴充性用生態現成的庫直接化解;且 Python 與框架規劃中的 AI 模組家族共用生態,壓測不必單獨養一種語言鏈。選 Locust 的理由收斂為一條:runtime 可擴充性 + Python 家族共用,而非「Locust 是更強的壓測引擎」(見「替代方案」——k6 在純壓測工程上多項更強)。

D2:perf 為新 module type,{app}-sim

壓測模組是 workspace 中與 server 平行、1:1 測其對外契約 surface 的模組,命名比照 {app}-server{app}-simperf 登錄為合法 module type(project.jsonmodules[].type),使 Python 成為框架的一等模組家族

D3:三面向資源觀測(觀測不變量)

一次可信的壓測必須同時採集三面向;缺 monitor 面則報告不可信

採集為什麼
controllerLocust master 聚合統計(吞吐、p50/p95/p99、失敗、例外)負載結果本身
monitor壓力產生機自身資源(glances)證明瓶頸不在壓測端——壓測機若先飽和,controller 數字失真。強制
sut受測系統逐層資源(app×N、DB、proxy)定位真瓶頸在哪一層

D4:里程碑手動 NFR 閘門,計畫定門檻,不 CI gate

  • 定位:壓測是 Server/對外契約 surface 的 NFR 驗證,不進 per-US 三階段循環,掛在里程碑/發版前。
  • 門檻來源:SLA 由「壓力測試計畫」定義(目標併發、延遲上限、錯誤率、各層資源紅線),「壓力測試報告」對照。
  • 執行時機:里程碑手動——壓測耗時耗資源、需受控環境,不適合 per-commit CI 常態化。框架以 CI 自動 gate,改由維護者依報告判定。
  • 前置:目標端點須功能已穩定,但不硬綁特定階段門檻(如 S.UAT)——下游執行方式無法預測,由計畫依實況認定。

D5:情境由生成產生,無 reference-surface(落地後修訂)

本決策於落地後修訂,並 supersede 早先「perf 沿用 @reference-surface」的取向。 原設計讓 locustfile 情境走 @reference-surface(比照業務碼:複製 + 逐一 retarget)。實作後認清 perf 與業務碼的非對稱——@reference-surface 是為無法機械推導的業務碼(entity、狀態機、業務規則)設計;perf 碼可推導(負載測試 pattern 有限,且「計畫 + app-server 源碼」在 workspace 皆可讀)。

故改採生成模型{app}-sim 只 ship 中性工具 sim_base(登入=appfuse auth 契約、三時間、named request 工具、池載入)+ uv 專案;persona/情境 locustfile.py/sim-plan --generate計畫 + server 源碼生成,是下游自己的碼——無 @reference-surface、不需 retarget/prune。m-reference-code 的標記機制與 /prune-reference 不涉 perf 模組(兩者已撤回 perf 相關內容)。sim_base 無業務內容,日後可原樣抽為發布套件 appfuse-sim(uv + Nexus pypi-hosted)。權威定義見 m-sim-common.md「生成模型」節。


替代方案 (Alternatives Considered)

方案為何不選
k6(Go 引擎 + JS 腳本)純壓測工程上多項更強(每 VU 資源效率、內建 thresholds/percentiles、CI 定位)。但腳本跑在沙箱化 JS runtime(goja),裝不了任意 npm/Python 庫;超出內建模組的需求(SSE per-token 計時、tiktoken 算 token、重用專案 SDK)須寫 Go 的 xk6 extension。與「seed 驅動 + 動態填表 + 重用 SDK」的實務需求相衝,且 Python 家族共用的效益拿不到
Gatling(JVM,Scala/Java DSL)與 server 同棧、效能佳;但 DSL 表達動態情境不如 Python 靈活,且不與 AI 模組家族共用生態
JMeterGUI 導向、腳本可維護性差、code-review 不友善
維持個案手工(不框架化)每下游重造、無 re-sync、fleet 漂移——正是本 ADR 要解的問題

翻盤條件 (Reversal Conditions)

D1(選 Locust)在下列情況天平會倒向 k6,屆時應重議:

  • 對外 API 以 gRPC 串流為主:k6 原生 gRPC 較強,而 Locust 的 Python gRPC 客戶端優勢收窄——「非標準客戶端」這條理由弱化。
  • 需求為極高併發、且壓測完全與 AI 模組解耦(不碰 AI 端點、不共用 Python 碼):此時 k6 的資源效率與 CI 定位優勢壓過 Python 家族共用的效益。

D4(不 CI gate)在壓測能穩定跑於受控、可重置的專用環境、且執行成本降到可接受時,可重議是否納入 pre-release CI。


後果 (Consequences)

正面

  • 三年生產實務的骨架(三面向觀測、seed 驅動、清理紀律、計畫→報告)成為框架慣例,下游 scaffold 即得、不重造、不漂移。
  • Python 成為一等模組家族,與未來 AI 模組共用生態。
  • NFR 驗證在方法論有了明確落點(Server/契約 surface、里程碑)。

負面 / 成本

  • 引入 Python 到原本 TS + Java 的技術棧(已由「AI 模組亦 Python」的方向吸收)。
  • 壓測為手動里程碑執行,仰賴維護者紀律;rule safety net 只約束 AI,擋不住人跳過(誠實限制,同其他兩層閘門機制)。
  • 安全 side 未涵蓋(見「相關」)。

落地產物與待辦

產物狀態
m-sim-common.md(Tier 2 common practice)✅ 已建立(三面向觀測、骨架四紀律、SLA 門檻、兩軌落點)
m-methodology / m-server-common / m-reference-code / m-module-inventory 交叉引用✅ 已接
module type perf 登錄(m-module-inventory 對照表 + scaffold type 表)✅ 已補
生成模型(撤回 @reference-surfacem-reference-code/m-reference-prune 已移除 perf 內容)✅ 已改(D5 修訂)
app-simsim_base 中性工具 + florist 生成示範,uv)✅ 已建
計畫驅動模型(persona × 情境 × 三時間)+ 花店壓測計畫✅ 已建(m-sim-common 不變量二 + florist-load.md
/sim-plan skill(author + --generate⏳ 設計中
/scaffold-module perf 接線(Step 5 複製、6.6 跳過 stamp、6.9 裁生成物 + retarget 工具)✅ 已補

  • 安全測試 NFR(SAST / DAST / SCA)為姊妹關注:與效能同屬跨切面 NFR,初步方向為 SonarQube(SAST,框架已用)+ OWASP ZAP(DAST)+ Trivy/Dependabot(SCA)。尚未拍板、未落地,留待獨立 ADR,避免把未決策的架構與本 ADR 的已決策混寫。
  • ADR-003: Headless 契約驅動 API 軌——對外契約 surface 的概念來源;壓測是其 NFR 面。
  • ADR-005: 方法論與實務分層——m-sim-common 為其定義的 Tier 2 common practice。
  • 方法論規則 m-sim-common.md——本 ADR 的規範落地。