一次 QPS 風暴的復盤:從 API 變慢到建立防禦層
Tags: middleware,, observability, qps,, rate-limit,
背景:從「API 變慢」開始的三天
客戶反應畫面變慢!!!
虫合 ~
只要開啟某個頁面,畫面反應就開始變慢 <— 我一開始以為偶發 太陽風暴 中共又雞歪切電纜
後來同事截圖 錄影 我才覺得 有毛病!!!
- 前端看起來像「系統掛了」,但其實是後端資源被耗盡
後來發現這個問題定義為:QPS(請求頻率)失控造成的系統壓力問題,而不只是單純的 SQL / 程式碼慢。
很明顯了 上面這句話完全不是我會說的,對! 我請GPT幫我寫,嘿嘿嘿!!!
症狀:壓測一跑就爆,還不確定有沒有擋到
我用簡單的壓測工具(或自寫腳本)模擬:
- Concurrency:數十
- Duration:數十秒
- 目標:同一支「查詢型 API」
結果是:
- QPS 大約落在 70~80 左右(依環境不同略有浮動)
- 大量 request 失敗
- 我一開始甚至不確定:後端到底是因為慢而失敗,還是我有成功回 429(Too Many Requests)
不是立刻開始狂優化 SQL,而是先把問題拆成兩個:
- 系統能不能「正確拒絕」(回 429 / 回應可預期)
- 系統在拒絕之前,是不是已經被 DB / CPU / Connection 拖垮
釐清:這不是「效能」而是「缺少防禦層」
一開始我的直覺是 SQL 或 DB index,因為查詢型 API 通常容易慢。
但觀察後發現:
- 即便單次查詢可以優化
- 只要短時間被無上限地打
- 依然會把 DB / 應用程式執行緒 / 連線池拖垮
所以我把解法順序改成:
1) 先補「防禦層」讓系統穩定
2) 再做效能優化讓平均體感更好
解法一:加上全域 Rate Limit(先讓系統會拒絕)
我先加上一層「全域」的 Rate Limit Middleware(概念如下):
- 超過上限:直接回 429 Too Many Requests
- 保留 burst(短時間允許一些尖峰)
- 優先保護整個系統,不要讓 DB 先被打爆
我怎麼驗證「真的有擋到」?
我做了兩件事:
- 壓測結果除了成功/失敗,我會特別統計 status code 分布(429 有沒有出現)
- 在 middleware 內加上可觀測性(log 或告警),確保每次拒絕都能追到
解法二:發現來源異常 → 加上 Origin / Tenant 驗證
在觀察 log 時,我注意到有一部分請求的來源不太正常。
我不想直接假設是攻擊或爬蟲,但我知道一件事:
如果某些租戶只能從指定的前端網域呼叫 API,
那就應該在 server 端做一層「Tenant + Origin」驗證。
於是我加上第二層 middleware(概念如下):
- 從驗證過的 token/context 取得租戶識別(已匿名化)
- 該租戶有設定白名單 origin(已匿名化)
- 若來源不在白名單:
- 阻擋 request
- 回應明確的 HTTP status(常見做法:403 / 401 / 400,依你的語意而定)
- 發告警到內部通知管道(已匿名化)
細節:我特別處理了 CORS Preflight
瀏覽器會送 OPTIONS(preflight),這種通常要直接放行,不然你會把合法前端也擋掉。
現在的整體流程(匿名化版本)
下面是我最後整理的 API 防禦層順序(只保留概念):
↓
Auth / Token Validation
↓
Tenant + Origin Allowlist Check (skip OPTIONS)
↓
Global Rate Limit (429 on limit exceeded)
↓
Business Handler (DB / Logic)
↓
Response
這次我學到的事:效能不是第一步,穩定性才是
以前我遇到 API 慢,很容易直覺「去救 SQL」。
但這次經驗讓我更確定:
- 沒有防禦層:再快的 SQL 也會被打爆
- 沒有可觀測性:你甚至不知道自己有沒有擋到
- 先讓系統可預期地拒絕,才有空間做真正的效能改善
後續可以做的優化方向(我給自己留的 TODO)
- 分層限流:全域 + per-tenant + per-endpoint(視需求)
- 更完整的指標:429 次數、來源分布、延遲分位數、DB 連線池使用量
- 前端降載:debounce、request 合併、快取、避免重複請求
- DB 層優化:在系統穩定後再回頭做 index / query 改寫
這三天我最大的收穫是:「系統工程」的順序很重要。 先讓系統會拒絕、會告警、會被觀測,才能談效能與體感。
這文章感覺幹話一堆 XDDD,根本農場文章!但是大概就是我這幾天的樣貌拉! XDDD
笑💩
以後都給 AI 寫文章就好拉! XDDD