# Day 6 閒聊如何量測系統的容量與 Baseline？

在公司已經有在營運且有穩定收入時，對於新系統規劃上，我們能加入對系統容量的估算與驗證。當然新創還沒賺錢的，不用想這件事情，這就是過早優化，重心應該放在開源賺錢 XD

> We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
> 
> Donald Knuth
> 
> The Art of Computer Programming (TAOCP)

設計跟執行這樣的量測與驗證其實很需要專業的知識與足夠的時間與成本。也幾乎跟測試自動化一樣。也很容易就成為嘴邊說很重要，但真的要做時都是沒時間 XD

> ```yaml
> 測試自動化老是名列調查中的前茅
> 可是大家總覺得他很困難
> 並且更神奇的都說沒時間做
> 感覺大家只是喜歡, 但是從未把它放在優先級上
> 
> 2024 Day13 軟體測試現狀調查
> https://ithelp.ithome.com.tw/articles/10343736
> ```

---

在現代軟體開發和運維中，系統容量（Capacity）和基準線（Baseline）是兩個至關重要的概念。它們對於確保系統在高負載情境下能夠穩定運行，並且在需要擴展時提供準確的參考指標，具有重要意義。然而，這兩個概念的具體含義和實施方法，對許多工程師來說仍然是一個比較模糊的領域。本文將通過閒聊的方式，深入探討如何量測系統的容量與 Baseline，並提供一些實際應用中的建議。

## 什麼是系統容量（Capacity）？

系統容量可以簡單地理解為系統在給定的資源條件下，能夠穩定處理的最大負載。這個負載可以是 RPS、QPS 或者是 TPS。系統容量的測量並不是一個靜態的過程，而是**動態**的，隨著系統的設計、配置和運行條件的變化而變化。

在 YouTube 搜尋 `Sytstem Capacity Estimation` 就會出現一堆相關影片了。這在 [`System Design Interview`](https://www.tenlong.com.tw/products/9798664653403?list_name=srh) 一系列的書中也是常見的系統設計內容。

## 什麼是基準線（Baseline）？

基準線（Baseline）是系統在特定條件下運行的性能指標，它反映了系統在正常情況下的性能表現。Baseline 的意義在於，它提供了一個衡量系統性能變化的標準，如果系統性能在實際運行中與 Baseline 偏離過多，則意味著系統可能存在問題或需要進行優化。

測量 Baseline 的過程，通常稱為基準測試（Benchmarking）。

### 系統容量的衡量指標

其實昨天就介紹過了，這裡只是再 recap。

* **RPS（Requests Per Second）**：每秒能夠處理的請求數量，通常應用於 API 服務或 Web 服務。
    
* **QPS（Queries Per Second）**：每秒能夠處理的查詢數量，多用於資料庫系統中。
    
* **TPS（Transactions Per Second）**：每秒能夠處理的交易數量，通常應用於金融系統中。
    

這些指標的計算方法基本相同，都是以總請求數或總操作數除以所需的總時間（以秒為單位）。比如，如果一個系統在 10 秒內處理了 1000 個請求，那麼該系統的 RPS 為 100。

## 系統容量的量測

### 量測系統容量的主要目的

1. **確保系統的穩定性和可靠性**：
    
    系統容量測試的首要目的是確保系統在高負載情境下依然能夠穩定運行。通過測試，可以提前識別出在不同負載下系統可能出現的瓶頸和故障點，從而進行優化，避免在實際運行中出現性能問題或系統崩潰。
    
2. **預測並規劃資源需求**：
    
    透過容量測試，可以確定系統在不同負載情況下所需的資源（如 CPU、記憶體、網路頻寬等）。這有助於預測未來的資源需求，從而進行合理的規劃和擴展，避免資源浪費或因資源不足導致的性能瓶頸。
    
3. **提升系統的性能表現**：
    
    通過容量測試，開發和運維團隊可以更好地理解系統在不同負載下的行為模式，從而進行有針對性的優化，提升整體的性能表現。例如，可以通過測試找到系統的最佳併發數量，從而優化併發處理能力，提升 QPS、TPS 等性能指標。
    
4. **支援業務增長**：
    
    在業務增長的過程中，系統的負載往往會隨之增加。通過容量測試，可以提前瞭解系統在應對業務增長時的表現，並進行相應的擴展計劃，確保系統能夠滿足未來的業務需求。
    
    在 Rick 大大的部落格中也提到業務導向的思路。從滿足業務目標的目的，找出整體系統容量需要多少。\[**如何量測系統的容量？（壓測）\](**[https://rickhw.github.io/2019/09/20/SQA/How-to-Measure-System-Capacity/](https://rickhw.github.io/2019/09/20/SQA/How-to-Measure-System-Capacity/))
    
5. **進行容量規劃和擴展設計**：
    
    在進行系統架構設計或擴展時，容量測試提供了基礎數據支持。測試結果可以用來指導系統的水平擴展（如增加伺服器節點）或垂直擴展（如升級硬體配置）的決策，從而設計出符合業務需求的高效架構。
    
6. **定義和驗證 Baseline**：
    
    容量測試還有助於定義系統的基準線，這是一個系統在正常運行下的性能指標。通過測試，可以驗證這些基準線是否合理，並據此進行持續的性能監控，及時發現和解決潛在的性能問題。
    
7. **滿足法規和 SLA 要求**：
    
    某些行業可能要求系統達到特定的性能標準，這些標準通常在服務水準協議（SLA）中有所規定。通過容量測試，可以確保系統符合這些法規要求，避免因不達標而引發的法律或商業風險。
    

這些目的共同指向一個核心目標：確保系統在不同的負載和業務情境下，能夠穩定、高效地運行，並且能夠持續支援業務的發展。

### 常見的容量測量策略

在進行系統容量測量時，根據不同的測試目標和需求，我們可以選擇不同的測量策略。以下是幾種常見的策略：

1. #### 以業務目標為基準
    
    這種策略側重於滿足特定的業務需求。例如，如果業務需求是系統能夠處理 200 TPS（每秒兩百筆交易），那麼測試的目標就是確定系統在達到該目標時所需的資源配置。這種策略通常用於制定系統資源配置計劃，並確保系統能夠在指定的業務需求下穩定運行。
    
    在這種策略下，測試過程中的重點是找出瓶頸並進行優化，以降低資源成本並提高系統效能。
    
2. **以系統資源為基準**
    
    這種策略側重於確定在現有資源配置下，系統能夠達到的最大負載。例如，在已知的資源配置下（如一台 c5.xlarge 伺服器），測試系統能夠達到的最大 TPS、RPS 或 QPS。這種策略適合用來評估系統在現有資源下的性能，並推算在不同負載情境下的表現。
    
    這種策略下的測試結果通常被用來推測當系統負載增加時，可能需要的資源配置。當測試超過系統的理想容量時，我們通常需要處理的是系統的可靠性問題。
    
3. **以應用程式設計為基準**
    
    這種策略主要用於應用程式的效能調校。例如，以 100 QPS 為基準，在 c5.xlarge 的配置下設計應用程式。目標是確保應用程式在這個基準下能夠穩定高效運行。
    
    在這種策略下，測試過程中需要深入了解記憶體管理、Multi-Thread、I/O 模型等技術細節，並對應用程式進行細緻的性能調優。
    
    這種測試策略通常被用來制定系統的 SLO，並進行效能測試，確保系統能夠滿足既定的服務水準。
    
    這裡其實都是為了省錢了 XD
    

### 系統容量的測量方法

系統容量的量測主要分為以下幾個步驟：

1. **資源準備**：首先，定義好待測目標和基本條件。待測目標可以是特定的 API、資料庫查詢等；基本條件則是系統所使用的硬體資源，如 CPU、記憶體、I/O 設備等。
    
    1. **待測目標**：可以是特定的 API、資料庫查詢、服務端計算等。選擇的目標應該能夠代表系統的主要負載情境。
        
    2. **基本條件**：指的是系統所使用的硬體資源，如 CPU、記憶體、I/O 設備等。這些資源應該根據測試目標的不同進行合理配置。在這裡任何設定都要盡量與正式的營運環境一致，不能營運環境用特別優化過得參數與高效能的硬碟，但測量環境都用預設和低IOPS等級的硬碟。
        
        在這一步驟中，我們需要確保系統架構圖已經準備好，並且所有相關的系統依賴和網路配置都已經確定。這樣可以避免測試過程中出現不必要的干擾，從而獲得更加準確的測試結果。解決干擾的策略就是讓基準測試運行更長時間，不是只有執行個數秒為單位，可能是以數分鐘為單位。因為太短的測試可能會遭遇很多因素而有影響。
        
2. **逐步增加負載**：使用負載測試工具（如 k6、JMeter）逐步增加請求量，直到系統開始出現性能下降或者錯誤率增高的情況，記錄此時的負載數值。這個過程被稱為「逐漸增壓」或「步進負載測試」。
    
    我們通過逐步增加請求量來測試系統的響應能力，並觀察系統在不同負載下的表現。當系統無法再穩定處理增加的負載時，即出現瓶頸，這個時候的負載數值即為系統的容量。這個數值應當被記錄下來，作為系統容量的基準數據。
    
    在測試過程中，我們應該注意收集以下數據：
    
    * **回應時間**：在不同負載下的平均回應時間。
        
    * **錯誤率**：系統在高負載下出現的錯誤比例。
        
    * **吞吐量**：系統在單位時間內處理的請求數量。
        
    * **資源使用率**：包括 CPU、記憶體、I/O 的利用率。
        
3. **觀察資源使用率**：在測試過程中，觀察系統資源的使用率。這包括：
    
    * **CPU 利用率**：查看 CPU 的使用情況，是否存在過載情況。
        
    * **記憶體消耗**：觀察記憶體的使用情況，是否存在記憶體洩漏或不足。
        
    * **I/O 操作**：分析 I/O 操作的效率，是否存在瓶頸。
        
        這些觀察可以幫助我們了解系統在不同負載下的資源消耗情況，從而更好地理解系統的性能極限。
        
4. **找到臨界點**：當系統無法再穩定處理增加的負載時，我們就找到了系統的臨界點，這個點即為系統的容量。臨界點的數據應該被詳細記錄，並作為系統的容量基準數據。
    
    找到臨界點後，我們可以進行多次測試並取平均值，以保證測試結果的準確性。這樣可以避免由於偶然因素導致的數據誤差。
    
5. 確定 Baseline︰通過 Baseline，我們可以了解系統在正常運行狀態下的性能，並將其作為後續優化和擴展的參考。
    
    確定 Baseline 的方法通常包括以下步驟：
    
    * **穩定性測試**：在確定系統容量後，使用穩定性測試來檢驗系統在該容量下的穩定性。這可以幫助我們找到 Baseline，並確保系統在實際運行中能夠保持穩定。
        
    * **性能數據收集**：在穩定性測試過程中，收集系統的性能數據，並將其作為 Baseline 的數據來源。
        
    * **多次測試驗證**：進行多次測試並比較結果，確保 Baseline 的準確性。
        
6. **多次驗證**：為了確保測試結果的準確性，建議進行多次測試並取平均值。這樣可以避免由於偶然因素導致的數據誤差。
    

### 理解測試結果與得出結論

在完成測試後，對測試結果進行分析和解讀是非常重要的。

1. #### 分析性能數據
    
    測試結束後，我們需要對測試過程中收集到的性能數據進行深入分析。這些數據包括回應時間、吞吐量、錯誤率、CPU 利用率、記憶體使用情況、I/O 操作等。通過分析這些數據，可以了解系統在不同負載下的表現，並找到系統的瓶頸所在。
    
2. 比較不同負載下的表現
    
    將系統在不同負載條件下的表現進行比較，確定系統在哪個負載範圍內能夠穩定運行，並找到超過該範圍後性能開始下降的臨界點。這有助於確定系統的容量極限，並為後續的擴展設計提供依據。
    
3. 得出結論與建議
    
    根據測試結果，得出有價值的結論。例如，系統在一定的併發數量下能夠穩定運行，但超過該併發數後，響應時間顯著增加，錯誤率上升。根據這些結論，提出相應的優化建議，如增加資源配置、優化程式碼性能、調整系統架構等。
    
4. 制定容量規劃
    
    根據測試得出的系統容量數據，制定容量規劃。這包括系統在不同負載下所需的資源配置，以及在未來負載增加時所需的擴展計劃。容量規劃可以幫助團隊提前準備資源，確保系統能夠在負載增加時保持穩定和高效運行。
    

## k6 如何幫助探索 Baseline？

在 [*OpenTelemetry 入門指南*](https://www.tenlong.com.tw/products/9786263338739) 的第 13 章介紹 k6 的各種負載測試類型時，有介紹到 Breakpoint test。其實我們要做的就是逐步增加系統負載。

> **斷點測試( Breakpoint test )**:測試的持續時間中等,流量與工作負載 會逐漸增加至某個點。通過逐步增加負載直到系統達到性能斷點或失敗 點,以識別系統的最大承受能力。主要用來確定系統或應用程式可以承 受的最大負載。

Postman 則是選擇**漸增負載(Ramp up )**。

下圖展示了 k6 各種負載測試類型，在測試時間長度與流量的關係。

![圖13-9.png](https://github.com/tedmax100/OpenTelemetryEntryBeook/blob/main/%E5%90%84%E7%AB%A0%E7%AF%80%E5%9C%96%E7%89%87/%E5%9C%9613-9.png?raw=true align="left")

剛好 k6 有 [`stages`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#stages) 與 [`thresholds`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#thresholds)，能幫我們實現這需求。

```yaml
export const options = {
  stages: [
    { duration: '3m', target: 10 },
    { duration: '5m', target: 10 },
    { duration: '10m', target: 35 },
    { duration: '3m', target: 0 },
  ],
  thresholds: {
    'http_req_duration': ['avg<100', 'p(95)<200'],
    'http_req_connecting{cdnAsset:true}': ['p(95)<100'],
    error_total_count:: [
      {
        threshold: "count<100",
        abortOnFail: true,
      },
    ],
  },
};
```

此外一些資源指標能通過 Prometheus的 [node\_exporter](https://github.com/prometheus/node_exporter) 來取得，例如︰

* 硬碟的IOPS
    

```yaml
# Read IOPS
rate(node_disk_reads_completed_total{instance=~"$hostname",device=~"[a-z]*[a-z]"}[5m])
# Write IOPS
rate(node_disk_writes_completed_total{instance=~"$hostname",device=~"[a-z]*[a-z]"}[5m])
```

* 硬碟 I/O 利用率
    
    ```yaml
    # 硬碟 I/O 利用率 (iostat中的%util,取值範圍[0-1])
    rate(node_disk_io_time_seconds_total{instance=~"$hostname"}[5m])
    ```
    
* #### **硬碟平均 I/O 隊列數量**
    

```yaml
rate(node_disk_io_time_weighted_seconds_total{instance=~"$hostname"}[5m])
```

* 硬碟讀寫延遲情況
    
    ```yaml
    # Read latency(ms)
    rate(node_disk_read_time_seconds_total{instance=~"$hostname"}[5m]) / rate(node_disk_reads_completed_total{instance=~"$hostname"}[5m]) * 1000
    # Write latency(ms)
    rate(node_disk_write_time_seconds_total{instance=~"$hostname"}[5m]) / rate(node_disk_writes_completed_total{instance=~"$hostname"}[5m]) * 1000
    ```
    
* CPU Busy 佔比情況，(0 - 100%)
    
    (所有 CPU 使用情形 - 5 分鐘內 CPU 空閒的平均值) / 所有 CPU 使用情形
    
    ```yaml
    (((count(count(node_cpu_seconds_total{instance=~"$hostname"}) by (cpu))) - avg(sum by (mode)(irate(node_cpu_seconds_total{mode='idle',instance=~"$hostname"}[5m])))) * 100) / count(count(node_cpu_seconds_total{instance=~\"$node:$port\",job=~\"$job\"}) by (cpu))
    ```
    
* CPU 處於等待 I/O 的時間佔比
    
    ```yaml
    sum by (instance)(rate(node_cpu_seconds_total{mode='iowait',instance=~"$hostname"}[5m])) * 100
    ```
    
* 網卡的上傳流量
    
    ```yaml
    rate(node_network_transmit_bytes_total{instance=~"$hostname"}[5m])
    ```
    
* 網卡的下載流量
    
    ```yaml
    rate(node_network_receive_bytes_total{instance=~"$hostname"}[5m])
    ```
    

很多硬體資源上的指標能參考。至於應用程式的指標日後在慢慢介紹。

### 結論

透過以上步驟，系統容量的測量不僅僅是一個單純的測試過程，更是一個系統性地理解和優化系統性能的重要環節。確定測試內容和深入理解測試結果，是從測量中獲得有用信息的關鍵。這樣，才能針對具體問題提出有效的解決方案，從而提升系統的整體性能和穩定性。
