一、把 Day 26 缺的那一塊補上
Day 26 結尾留了一個問題。你量出了 QuickPizza 的 LCP——但那是安靜時的 LCP:整個網站只有你一個瀏覽器 VU,伺服器閒著,愛多快有多快。而使用者抱怨慢的時刻,從來不是安靜的時刻——是尖峰,是 Day 1 的搶票之夜,是所有人同時湧進來的那十分鐘。你真正想知道的是:三十個人同時在用的時候,第三十一個人打開頁面,看到什麼?
直覺的做法是開三十個瀏覽器 VU——Day 26 的成本帳已經否決了它:三十個 Chrome 會先壓垮你的筆電,量到的是施壓端崩潰(Day 19 的老朋友)。業界的標準解法便宜得多:人潮不需要是真的瀏覽器,只有「量體感的那一個」需要是真的。協定層 VU 便宜,就讓它們去演人潮——三十個協定層 VU 對 API 持續施壓,把伺服器推到有負載的狀態;同時一到三個瀏覽器 VU 走進來,像真使用者一樣開頁面,量下負載之下的 LCP。

圖 1:混合場景——便宜的協定層 VU 演人潮,一兩個真瀏覽器在人潮中量體感
這個模式一句話講完:背景負載製造條件,瀏覽器探針量測結果。它是 Day 13 與 Day 26 的合體——Day 13 教的 scenarios 讓一支腳本能同時跑多個場景,當時的例子是「瀏覽的人與下單的人」;今天兩個場景一個在協定層、一個在瀏覽器層,語法一模一樣,只是其中一個 scenario 換了引擎。
二、組裝:兩個世界裝進同一支腳本
讓 Claude Code 把 Day 14 的 journey 和 Day 26 的 browser 腳本合體:
Prompt 1|混合場景腳本
請把 tests/journey.js(協定層 journey)和 tests/browser-home.js(瀏覽器層)
合併成一支混合場景腳本 tests/mixed.js:
- scenario "background":協定層,ramping-vus,5 分鐘爬到 30 VU 後維持 5 分鐘,
跑原本的 journey 流程(含 think time)
- scenario "probe":瀏覽器層,1 個 VU,等 background 爬滿後才開始
(用 startTime 延後 5 分鐘),之後每分鐘開一次首頁並完成點披薩流程,共 5 次
- thresholds 分層設:
協定層:http_req_duration p(95)<800、http_req_failed rate<0.01
瀏覽器層:browser_web_vital_lcp p(75)<4000(負載下放寬,理由請見文章)
- 兩個 scenario 的指標我要能分開看:請用 tags 或 scenario 名稱區隔,
並告訴我 summary 裡怎麼分辨誰是誰
- 逐段解釋,特別是 startTime 的計算
骨架長這樣(合併後的完整版以 Claude Code 產出為準):
import { browser } from 'k6/browser';
import http from 'k6/http';
import { sleep, check } from 'k6';
export const options = {
scenarios: {
background: { // 人潮:便宜的協定層
executor: 'ramping-vus',
exec: 'apiJourney',
startVUs: 0,
stages: [
{ duration: '5m', target: 30 }, // 爬到尖峰
{ duration: '5m', target: 30 }, // 維持,讓 probe 在這段量
],
},
probe: { // 探針:一個真瀏覽器
executor: 'per-vu-iterations',
exec: 'uiProbe',
vus: 1,
iterations: 5,
startTime: '5m', // 等人潮爬滿才進場
options: { browser: { type: 'chromium' } },
},
},
thresholds: {
http_req_duration: ['p(95)<800'],
http_req_failed: ['rate<0.01'],
browser_web_vital_lcp: ['p(75)<4000'], // 負載下的門檻,見下文
},
};
export function apiJourney() { /* Day 14 的 journey,原封不動 */ }
export async function uiProbe() { /* Day 26 的流程,每次之間 sleep(60) */ }
三個設計決定值得說明。第一,probe 要等人潮爬滿。startTime: '5m' 讓瀏覽器探針在背景負載到頂後才進場——你要量的是「尖峰時的體驗」,不是「人潮還在進場時的體驗」;在爬坡段量到的數字,每一次的負載條件都不同,Day 17 的四同原則會被自己違反。第二,探針每分鐘量一次、共五次。LCP 是每次載入一個樣本,五個樣本才能談 p75;隔一分鐘是避免探針自己的快取與連線重用把數字做漂亮(Day 10 的快取指紋、Day 19 的連線重用,兩個老陷阱在瀏覽器層一樣存在)。第三,負載下的 LCP 門檻放寬到 4 秒。安靜時用 Google 的 2.5 秒沒問題;負載下要不要放寬、放寬到多少,是 Day 16 的功課——回到業務問「尖峰時使用者忍到幾秒會走」,這裡的 4 秒是「待改善的上緣」,一個可以被挑戰的起點,不是標準答案。
執行前的資源提醒:這支腳本同時跑 30 個協定層 VU 和 1 個 Chrome,施壓端的負擔是「Day 12 的 load」加「Day 26 的一個瀏覽器」——筆電扛得動,但 Day 19 的習慣照做:跑的時候盯一眼自己的 CPU,確認探針的數字不是被自己拖慢的。
三、判讀:兩層數據的四種組合
跑完之後你手上有兩層數據:協定層的 p95(背景人潮感受到的 API 速度)和瀏覽器層的 LCP(探針在人潮中的真實體感)。兩層各自有好壞,交叉起來四種組合,每一種都指向不同的地方:

圖 2:兩層數據的四種組合——交叉判讀,問題的位置自己浮出來
組合一:協定層好、瀏覽器層好。負載下 API 快、體驗也快——這是你要的驗收結果,寫進 Day 21 報告的結論段:「30 VU 背景負載下,API p95 420ms,頁面 LCP p75 2.8s,均在門檻內。」注意這句話的資訊量比單層測試大得多:它同時保證了後端與體感。
組合二:協定層好、瀏覽器層差。API 在負載下仍然快,但 LCP 崩了。嫌疑人在前端與資產:負載讓靜態資源(圖片、JS 檔)變慢了——API 有快取撐著、靜態資源的頻寬卻被人潮吃掉;或者頁面要發的請求太多,每個都快,加起來慢(瀑布問題)。這是 Day 26 分家邏輯的負載版:TTFB 與 API 數據還你後端清白,單子開給前端與基礎設施(頻寬、CDN)。
組合三:協定層差、瀏覽器層差。API 在負載下就慢了,體感自然差。這其實是最單純的組合:問題回到協定層,Day 18 的三條線索、Day 24 的趨勢判讀全部適用——先修後端,修完再回來量一次 LCP,通常會一起好;不會一起好,才輪到組合二的前端嫌疑人。
組合四:協定層差、瀏覽器層好。最反直覺的一格,但真的會出現:API p95 超標,頁面體感卻正常。兩種常見原因:頁面根本沒用到那個慢的端點(用 Day 15 的 tags 對一下,慢的是不是 probe 流程外的 API);或者前端做了聰明的事——快取、骨架屏、非同步載入,把後端的慢藏起來了。這一格的價值是提醒:協定層紅燈不必然等於使用者受害,開單時的業務影響描述(Day 22 要素三)要據實寫,不要拿使用者當免死金牌。
四種組合整理成表,跑完混合場景先對號入座:
判讀交給 Claude Code 時,把兩層一起餵:
Prompt 2|混合場景判讀
這是 mixed.js 的 summary(含協定層與 browser_web_vital_* 指標)。請:
1. 分層整理:background 的 p95/錯誤率/rps;probe 的 LCP/CLS/INP/TTFB(p75)
2. 對照四種組合,判斷這次落在哪一格,說明證據
3. 若落在「協定層好、瀏覽器層差」:用 TTFB 與資產載入的角度列三個前端嫌疑人
4. 與我上次安靜時的 browser 測試結果(附上)比較:負載讓 LCP 劣化了幾成?
哪個 Web Vital 劣化最多?每個推論標註〔推論〕
四、動手做:安靜 vs 人潮的對照實驗
練習一:先量安靜的基準。單獨跑 Day 26 的 browser 腳本 5 次 iteration,記下 LCP/CLS/INP 的 p75。這是「無負載基準」——Day 17 的相對比較原則:要說「負載讓體驗變差」,先要有安靜時的數字。
練習二:跑混合場景。用 Prompt 1 的 mixed.js 完整跑一輪(約 11 分鐘)。跑的時候開著 Day 20 的 Web Dashboard,你會看到一個有趣的畫面:http 請求的曲線密密麻麻(人潮),每隔一分鐘探針的頁面載入穿插其中。結束後用 Prompt 2 判讀,對號入座四格中的一格。
練習三:把人潮加倍,看體驗的劣化曲線。把 background 的 target 從 30 改成 60,再跑一次。現在你有三個點:安靜、30 VU、60 VU 下的 LCP p75。把三個點排成一行——這就是「體驗隨負載劣化」的曲線雛形,也是給 PM 看的最好一張圖:不是「系統撐得住幾人」,而是「幾人的時候,使用者看到的畫面變成幾秒」。QuickPizza 是共用練習站,60 VU 是上限,不要再往上。
練習四(選做):組合四的親身版。把 background 的 journey 改成只打一個 probe 流程用不到的端點(請 Claude Code 幫忙挑),把它壓到超標。再跑混合場景——你會親眼看到協定層紅燈、LCP 卻無感,把「紅燈不必然等於使用者受害」變成自己的經驗。
五、注意事項:混合場景的六個習慣

給 RD 的一句話:混合場景的結果值得前後端一起看十分鐘:後端看 background 的 p95 確認負載下的 API;前端看 probe 的 LCP 與 TTFB 的差——那一段就是資產與渲染在負載下的表現。組合二(API 好、LCP 差)出現時,頻寬與 CDN 的討論就有了數據;組合四出現時,請幫 QA 一起確認慢端點到底在不在使用者的路徑上。
六、觀念驗證:三個問題確認你有帶走今天的重點
• 為什麼人潮用協定層 VU 演、只有探針用真瀏覽器?直接開三十個瀏覽器 VU 會發生什麼事?(第一節)
• 探針為什麼要 startTime 等人潮爬滿?負載下的 LCP 門檻為什麼不直接沿用 2.5 秒?(第二節)
• 「協定層差、瀏覽器層好」這一格為什麼會出現?它對 Day 22 開單的業務影響描述有什麼提醒?(第三節)
七、小結
Day 26 量的是安靜時的體驗,今天補上了負載:便宜的協定層 VU 演人潮,一個真瀏覽器當探針,在人潮中量真實體感——這是業界量「負載下的體驗」的標準模式,也是 Day 13 的 scenarios 最完整的一次兌現。判讀是一張二乘二的表:兩層各自的好壞交叉,問題的位置自己浮出來——前端、後端、或者「紅燈其實沒傷到使用者」。三個負載點連成的 LCP 劣化曲線,是給 PM 最好的一張圖。到今天,HTTP 的世界已經走完:協定層、瀏覽器層、兩層混合。但不是所有東西都是 HTTP——聊天室的長連線、金融業的 SOAP、微服務之間的 gRPC,k6 怎麼辦?——Day 28 見。