一、一種測試設定,回答不了六種問題
到目前為止,我們的測試都是「固定人數跑固定時間」。這回答得了「這個 API 健康嗎」,但職場上的提問遠不止於此:「雙十一那天撐得住嗎」「開賣那一秒會不會掛」「連續跑一週會不會越來越慢」「我們的極限到底是多少人」——每個問題需要的流量長相都不同。負載模型就是這件事的分類學:六種模型,六種問題。

圖 1:同一家餐廳的六種考驗——你想回答哪個問題,就辦哪一場
六種模型的正式對照:
Breakpoint 值得特別介紹一下身世:它直接回應 Day 1 那條曲線留下的問題——舒適區、警戒區、崩潰區的邊界到底在哪。做法是階梯式一路加壓,直到系統跌破你訂的門檻,那一刻的負載量就是答案。
二、Stages:描述形狀的語言
六種模型的差別,本質上是「VU 數隨時間變化的形狀」不同。k6 用 stages 描述形狀——一串「在多久之內,把人數變成多少」的指令:
export const options = {
stages: [
{ duration: '30s', target: 5 }, // 30 秒內從 0 爬到 5 人
{ duration: '1m', target: 5 }, // 維持 5 人跑 1 分鐘
{ duration: '15s', target: 0 }, // 15 秒內降回 0
],
};
三行就是一個 Load 模型的形狀:爬升、平穩、下降。六種模型的形狀全貌:

圖 2:六種模型的形狀——橫軸時間、縱軸 VU 數;形狀不同,回答的問題就不同
為什麼 Load 要「爬升」而非直接開到目標人數?兩個理由:真實流量本來就是漸進的(沒有一秒內湧進全部使用者的日常),而且爬升過程本身就是資訊——回應時間在哪個人數開始變化,一目瞭然。至於瞬間湧入的情境,那是 Spike 的專屬劇本,不是日常的樣子。
三、動手做:小流量跑出三種形狀
重申本篇的鐵則:我們在公開練習站示範的是「形狀」,全程不超過 10 VU。形狀學會了,實戰時把數字換成你公司環境的量——大流量本來就該發生在自己的環境(Day 6 五問走完之後)。
練習一:Load 的爬升與平穩
Prompt 1|生成 Load 形狀腳本
請把 pizza-api-test.js 另存為 shape-load.js,改用 stages:
30 秒爬到 5 個 VU、維持 1 分鐘、15 秒降回 0。
其他內容不變。並解釋 stages 每一行的意思。
執行時盯著終端機上的即時進度:VU 數會沿著你設計的形狀移動。跑完看輸出的 vus 一列——min 與 max 印證了整段旅程。
練習二:Spike 的突刺
Prompt 2|生成 Spike 形狀腳本
另存為 shape-spike.js,stages 改成:
10 秒維持 2 個 VU、5 秒內衝到 10 個、維持 30 秒、
5 秒內降回 2 個、再維持 20 秒。
解釋這個形狀在模擬什麼真實情境。
觀察重點在突刺的當下與退去之後:衝上去那幾秒的回應時間有沒有抖動?退回低流量後,數字有沒有回到突刺前的水準?後者就是「恢復能力」——Spike 測的不只是撐不撐得住那一下,還有事後回不回得來。
練習三:Breakpoint 的靈魂——跌破門檻自動停
Breakpoint 的精髓不在階梯加壓,而在「跌破門檻的那一刻自動停止」——k6 的 abortOnFail 就是這個煞車。真實的 breakpoint 需要把系統壓到跌破,練習站壓不倒(也不該壓倒),所以我們用一個聰明的替代:把門檻訂在不可能達到的嚴格值,讓煞車機制「假裝」被觸發,親眼看它怎麼運作:
export const options = {
stages: [ // 階梯:每 20 秒加 2 人
{ duration: '20s', target: 2 },
{ duration: '20s', target: 4 },
{ duration: '20s', target: 6 },
{ duration: '20s', target: 8 },
{ duration: '20s', target: 10 },
],
thresholds: {
http_req_duration: [{
threshold: 'p(95)<50', // 練習用:訂一個不可能達到的門檻
abortOnFail: true, // ★ 跌破就立刻停止整個測試
delayAbortEval: '10s', // 開跑 10 秒後才開始評估(暖身緩衝)
}],
},
};
跑起來大約十幾秒後,測試會自己停下:
ERRO[0014] thresholds on metrics 'http_req_duration' were crossed;
at least one has abortOnFail enabled, stopping test prematurely
這就是 breakpoint 的完整劇本:階梯加壓+跌破即停,停下那一刻的 VU 數與吞吐量,就是「極限」的答案。實戰時的差別只有兩處:門檻改訂在真實的 SLO(例如 p(95)<800),
階梯開到足以壓垮測試環境的高度。另外注意 abortOnFail 是溫和停止——Day 9 說過的 teardown 會照常執行,清理不會被跳過。
四、第一次該壓多少:四步土法估算
學會了形狀,下一個問題立刻出現:target 該填多少?有監控數據就用數據;但很多測試人員的現實是拿不到數據。這時用打聽得到的業務數字反推——不完美,但講得出依據,而「講得出怎麼估的」正是 Day 7 門檻精神的延伸:

圖 3:四步土法估算——業務量、每秒請求、一輪耗時、VU 數;從一半起步爬到 1.5 倍
完整例題:你問客服與 PM 得知「尖峰時段一小時約 3,600 筆訂單」,RD 說一筆下單流程前後約 10 個 API 請求。換算:每秒請求數 rps=3,600 筆 × 10 個請求 ÷ 3,600 秒=10。腳本裡一輪的耗時=回應時間約 0.5 秒+think time 3 秒=3.5 秒。估 VU=rps × 一輪耗時=10 × 3.5 ≈ 35。
起步策略:Load 測試從估算值的一半(約 18 VU)開始爬,最高爬到 1.5 倍(約 53 VU),邊爬邊看回應時間的變化。為什麼不直接開 35?因為估算的每個環節都有誤差,爬升的過程就是在用實測校正你的估算——爬到 20 就開始變慢,代表估少了別硬上;爬到 53 還一路平穩,下次就有底氣加高。把估算寫進測試筆記,之後拿到真實監控數據時回頭對照,你的估算能力會一次比一次準。
五、注意事項:選型與跑型的坑

給 RD 的一句話:QA 來問「一筆下單流程大概幾個 API 請求」時,那是土法估算的第②步——給個大概數字加上「哪幾個最重」,他的負載模型會準很多,而最重的那幾個端點,往往也是你最想先知道極限的地方。
六、觀念驗證:三個問題確認你有帶走今天的重點
• 行銷說下週三晚上八點整開搶限量商品——六種模型裡該跑哪一種?形狀的哪一段最需要仔細看?(第一、三節)
• Breakpoint 為什麼必須配 abortOnFail?沒有它會發生什麼事——對系統、對 Day 9 的清理各有什麼影響?(第三節練習三、第五節)
• 完全沒有監控數據,主管問「你這 35 VU 哪來的」——把四步估算完整講一遍,並說明為什麼從一半起步。(第四節)
七、小結
今天把「一種測試」升級成「六種問題各有對應的形狀」:smoke 問活著嗎、load 問日常、stress 問極端日、spike 問瞬間與恢復、soak 問長期、breakpoint 問極限——而 breakpoint 正式回答了 Day 1 那條曲線的懸念。stages 是描述形狀的語言,abortOnFail 是找極限的煞車,四步土法估算則讓你在沒有數據的現實裡,仍然講得出「第一次壓多少」的依據。從此設計測試的第一句話不再是「開幾個 VU」,而是「這次要回答什麼問題」。
附錄:六種模型的 stages 範本
// Smoke:極小流量健康檢查
stages: [{ duration: '1m', target: 2 }]
// Load:爬升-平穩-下降(數字換成你的估算值)
stages: [
{ duration: '5m', target: 35 },
{ duration: '20m', target: 35 },
{ duration: '3m', target: 0 },
]
// Stress:一路爬過預期值(例:1.5 倍)
stages: [
{ duration: '5m', target: 35 },
{ duration: '5m', target: 53 },
{ duration: '5m', target: 0 },
]
// Spike:低量-衝頂-回落-觀察恢復
stages: [
{ duration: '1m', target: 5 },
{ duration: '20s', target: 60 },
{ duration: '2m', target: 60 },
{ duration: '20s', target: 5 },
{ duration: '3m', target: 5 },
]
// Soak:中等流量長時間(時長依需求 2-8 小時)
stages: [
{ duration: '5m', target: 20 },
{ duration: '4h', target: 20 },
{ duration: '5m', target: 0 },
]
// Breakpoint:階梯加壓+跌破即停
stages: [
{ duration: '2m', target: 20 }, { duration: '2m', target: 40 },
{ duration: '2m', target: 60 }, { duration: '2m', target: 80 },
]
thresholds: {
http_req_duration: [{ threshold: 'p(95)<800',
abortOnFail: true, delayAbortEval: '30s' }],
}