# 緩慢接收與 API 回應的實驗資料

測量日期為 2026-11-02。文章數值僅統計正式測量。

## 條件

Linux 6.12 / aarch64 / glibc 2.36、CPython 3.11.2、Gunicorn 23.0.0、NGINX 1.22.1（Debian 1.22.1-9+deb12u10）。

- 主要條件：Gunicorn sync 2 個 worker；直接存取、NGINX buffering off、on 三條路徑；低速連線 0、1、2 件；每個條件 3 次。
- 輔助條件：Gunicorn gthread 1 個 worker、4 個執行緒；直接存取；低速連線 2 件；3 次。並行處理數不同，不能據此判定 worker 模型的效能優劣。
- `/fast`：12 位元組 JSON，每秒 8 件，以 125ms 間隔發送 9 秒，每次測量 72 件。不等待上一個回應完成才發送下一個要求。
- `/large`：同一塊 64KiB 重複 256 次，共 16MiB。每個低速用戶端約以 2MiB/s 讀取，約 8 秒完成。低速連線開始後約 250ms 開始 fast 要求。
- 低速端 SO_RCVBUF 設定 8KiB，Linux 回傳 16KiB。asyncio StreamReader limit 為 16KiB。這是控制接收程式的讀取節奏，不是限制真實網路頻寬。
- 所有 HTTP 使用同一主機的 127.0.0.1、HTTP/1.1、明文，回應後關閉連線。無 TLS、壓縮或快取。未對既有應用程式或公開 URL 發送測量要求。
- CPU 上限：應用程式 0.25 核心、NGINX 0.20 核心、控制與負載產生器 0.20 核心；配額週期 10ms。記憶體上限依序為 128MiB、96MiB、128MiB。
- 除 buffering on/off 外，NGINX 設定相同：proxy_buffer_size=16k、proxy_buffers=4×16k、proxy_busy_buffers_size=32k、proxy_max_temp_file_size=32m／要求、proxy_temp_file_write_size=64k。NGINX 以單一行程執行，master_process off。

## 檔案與統計

- `conditions.csv`：主要條件彙整。各次測量先計算 nearest-rank p50/p95，再取 3 次中位數。p95_min/max 是 3 次的最小／最大值。app_send_s 與 client_receive_s 先取每次 2 個回應的中位數，再取 3 次中位數。temp_peak_mib 是 3 次觀測到的最大邏輯大小合計。
- `runs.csv`：30 次完成測量。CPU core 為 cgroup CPU 時間增量／測量時間，1.0 代表一個核心。throttled_ms 是同區間 cgroup throttled_usec 增量。memory_peak_bytes 是約 200ms 取樣的 memory.current 最大值，不一定是真正瞬間峰值。rss_peak_bytes 是 cgroup 內行程 RSS 合計的最大值，可能重複計入共享頁面。
- `requests.csv`：全部 2,160 個 `/fast` 要求。時間以每次測量開始為零點。latency_ms 是實際開始至本文接收完成；schedule_lag_ms 是預定至實際開始；connect_ms 是 TCP 建立時間。status 全部為 200。
- `large-responses.csv`：33 個 `/large` 回應。receive_s 從 TCP 建立後發送要求，量到依讀取節奏消耗最後區塊完成。app_send_s 從回應產生器開始，量到伺服器寫完最後區塊之後，不代表資料已抵達使用者。
- `resource-samples.csv`：約 200ms 間隔的時序。temp_logical_bytes 是 NGINX 開啟中 proxy_temp 檔案的邏輯大小合計；temp_allocated_bytes 是 st_blocks×512 合計。已移除名稱但仍開啟的檔案也透過 /proc/PID/fd 計入，以 inode 去重。這不是實體 I/O 量。memory_bytes 是 cgroup memory.current，不是 RSS。
- `app.py`、`experiment.py`：正式測量使用的應用程式與測量程式。不含帶有執行主機名稱或憑證的 SSH 啟動器，也不含原始內部日誌。
- `dependencies.json`：發行檔 URL、下載位元組數、SHA-256 與 NGINX 套件版本。

API 延遲圖的點是 3 次中位數，線是最小至最大值。寫入／接收長條圖取低速 2 連線共 6 個回應的中位數；黑點是個別寫入時間。時序圖各路徑選 p95 居中的一次。各圖已標示不同的統計單位。

## 重現

標準函式庫測量程式需要 Linux systemd 使用者服務與 cgroup v2。`prepare` 下載 Debian bookworm-security arm64 索引與發行檔，解壓到專用目錄，不在主機安裝套件。使用的是當前索引，因此重跑時的 NGINX 套件可能與測量時不同。要完全一致，請確認 `dependencies.json` 的 URL 與 SHA-256。

以下範例假設 Linux 主機符合原始條件，且有足夠資源。這不是對既有服務或叢集套用設定的步驟。

```sh
lab_root=$(mktemp -d /tmp/kamui-slow-client-XXXXXXXX)
cp app.py experiment.py "$lab_root/"
python3 "$lab_root/experiment.py" --root "$lab_root" --stage prepare
lab_unit="$(basename "$lab_root")-controller.service"
systemd-run --user --wait --pipe --collect --unit "$lab_unit" \
  -p CPUQuota=20% -p CPUQuotaPeriodSec=10ms -p MemoryMax=128M \
  -p MemorySwapMax=0 -p TasksMax=24 -p RuntimeMaxSec=720 -p TimeoutStopSec=3 \
  /usr/bin/python3 "$lab_root/experiment.py" --root "$lab_root" \
  --stage main --controller-unit "$lab_unit"
```

程式會在 finally 停止各次測量的伺服器。結束後先收集結果，確認本實驗專用服務與行程都已結束，再只清理自己建立的專用暫存目錄。

## 排除項目與限制

8MiB／1MiB/s 與 16MiB 的探索性預備測量不混入正式統計。由 8MiB／1MiB/s 改成 16MiB／2MiB/s，同時改了回應大小與讀取速率，不能把差異歸因於大小。正式測量曾因整台主機 CPU 超過門檻而安全停止一次；排除未完成測量，保留完成的 17 次，再續跑 13 次，安全門檻與應用程式條件未變。

receive_s 量到每次讀取後的速率調整等待完成，包括最後一次讀取後的等待。因此約 8 秒包含節奏控制，不是真實網路的純傳輸效能。gthread 輔助條件同時改變 worker 模型、行程數與並行處理數，無法分離模型效果或找出最佳處理數。

應用程式是未使用框架的手寫 WSGI。未比較 Flask、Django、FastAPI、非同步模式或實際 PaaS 服務配置。未蒐集個別 TCP 緩衝區用量或 worker 堆疊，必須區分實測寫入時間、API 延遲、暫存檔案與依規格做的解釋。

這不是對所有條件施壓至最大吞吐量的實驗。未測量 OS 各 TCP 緩衝區容量、TCP_INFO、真實網際網路、TLS、HTTP/2/3、SSE、WebSocket、緩慢上傳、長時間、大量連線或磁碟耗盡。不能保證其他 Web 伺服器或 PaaS 的效能。
