緩慢接收與 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 主機符合原始條件,且有足夠資源。這不是對既有服務或叢集套用設定的步驟。
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 的效能。