← 返回 Roadmap
Stage 0 · HTTP Client Reliability

HTTPX:Timeouts、Deadlines 與 Resource Limits 深入版

先用圖看懂 request pipeline、四種局部 timeout 與 overall deadline,再深入 connection pool、cancellation、idempotent retry、FastAPI lifecycle、observability 與 fault injection。

Roadmap 定位
這是 System Design Roadmap 階段 0 的 HTTP client 深入教材。
開啟互動 HTML 版

本教材沿用「生活直覺 → 工程術語橋接 → High-level 定義 → 底層機制 → 實作決策」的閱讀順序。目標不是背四個 timeout 名稱,而是能在 FastAPI 服務呼叫下游時,精確回答:時間花在哪裡、哪個資源已飽和、取消發生在哪一端、是否可以 retry,以及如何用測試證明設定真的有效。


0. 先用圖建立全貌:兩條時間軸、四個局部關卡

在讀任何設定值之前,先把一次 request 看成「穿過多個關卡」,並把局部 timeout 與整體 deadline 分開。後面的所有 exception、pool sizing 與 retry 判斷,都能放回這兩張圖定位。

0.1 一次 HTTP request 經過哪些關卡?

  • Pool timeout 發生在 B。
  • Connect timeout 發生在 C。
  • Write timeout 發生在 D。
  • Read timeout 發生在 F。
  • E 的 server execution 並不完全受 client timeout 控制。

0.2 局部 inactivity timeout 與整體 deadline

局部 timeout 問「這一段多久沒有進展」;overall deadline 問「整個 operation 還剩多少生命」。不要用四個局部 timeout 的數值相加,假裝那就是可靠的 end-to-end budget。


1. 先建立完整心智模型:一次 HTTP 呼叫不是一個等待

1.1 生活直覺:餐廳外送不是只有「等餐」

一次外送訂單可能依序經過:等待外送員名額、前往餐廳、交付訂單、餐廳分批出餐、把餐送回。若只說「最多等五秒」,你無法知道五秒限制的是哪一段,也無法判斷超時後餐廳是否仍在做餐。

HTTP client 的一次 request 也有多個階段:

  1. 從 connection pool 取得可用連線,或取得建立新連線的容量。
  2. DNS、TCP connect、TLS handshake,必要時還有 proxy tunnel。
  3. 傳送 request headers 與 body。
  4. 等待 response headers 與 body chunks。
  5. 完整消費或關閉 response,決定連線能否回到 pool 重用。

1.2 工程術語橋接

外送比喻 HTTPX/網路術語 主要限制
等外送員名額acquire connection from poolpool timeout、max connections
前往餐廳DNS/TCP/TLSconnect timeout
交付訂單write request byteswrite timeout
等待每一批餐receive response chunksread timeout
整趟訂單時限end-to-end deadlineasyncio.timeout()/上游 deadline

1.3 High-level 定義

HTTPX 的 connect、read、write、pool timeout 是不同 I/O 階段的等待上限。它們主要防止某個網路操作或資源等待長時間沒有進展;它們不是「整個業務操作必須在 N 秒內完成」的完整 deadline。

1.4 一條 request 時間線

text
caller starts
    │
    ├─ pool wait ─────────────── PoolTimeout
    ├─ DNS/TCP/TLS ───────────── ConnectTimeout
    ├─ request body chunks ───── WriteTimeout
    ├─ response chunks ───────── ReadTimeout
    └─ parse / application work
caller returns

同一次 request 可能只經過其中部分階段。若 keep-alive connection 可重用,通常不會重新做 TCP/TLS;若 response body 很小,write/read 階段也可能極短。


2. Timeout 到底保證什麼?先拆掉三個危險誤解

2.1 Timeout 不是成功時間的承諾

read=5.0 不代表 request 一定在五秒內完成。HTTPX 官方定義是等待 response data chunk 的上限;若 server 每四秒送一小段資料,總下載可以遠超過五秒而不觸發 read timeout。

2.2 Timeout 不是遠端取消協議

client 停止等待,不代表 server 的 handler、database transaction 或第三方 side effect 被回滾。client 可能關閉 socket,但遠端程式可能已經:

  • 寫入 database 並 commit。
  • 發送 email、扣款或發布 message。
  • 還在 CPU 計算或等待另一個 downstream。

因此 timeout 後直接 retry 非冪等 POST,可能把一次不確定結果放大成重複 side effect。

2.3 Timeout 不是 failure detector

Read timeout 只能告訴你「在設定時間內沒有收到下一段資料」,不能證明:

  • server crash。
  • 網路完全中斷。
  • server 尚未開始處理。
  • server 沒有 commit。

精確術語是 local observation:caller 在自己的等待邊界內沒有觀察到預期進展。

2.4 Timeout、deadline、cancellation 的差異

概念 問題 範圍
Timeout某個操作最多等多久?pool/connect/read/write 等局部階段
Deadline整個工作最晚何時完成?多次 request、retry、parse、DB 等完整鏈路
Cancellationcaller 是否要求停止本地 task?本地 coroutine/task;不自動回滾遠端
Server timeoutserver handler/statement 最多跑多久?遠端自己的執行邊界

3. 四種 HTTPX timeout 的正式邊界

3.1 Connect timeout

Connect timeout 限制建立到目標 host 的連線所能等待的時間。實際路徑可能包含 DNS lookup、TCP handshake、TLS handshake、proxy connect;具體階段由 transport 與網路環境決定。

python
import httpx

timeout = httpx.Timeout(
    connect=2.0,
    read=5.0,
    write=5.0,
    pool=0.5,
)

async with httpx.AsyncClient(timeout=timeout) as client:
    response = await client.get("https://api.example.com/health")

Connect timeout 常見原因:錯誤 IP、firewall 丟包、DNS 過慢、TLS endpoint 不回應、proxy 無法建立 tunnel。它和 ConnectError 不同:timeout 是超過等待上限;error 可能是立即 connection refused、DNS failure 或 TLS error。

3.2 Read timeout

Read timeout 限制等待下一個 response data chunk 的時間。它不是整份 response 的總下載時間,也不只是「等待第一個 byte」。

python
timeout = httpx.Timeout(5.0, read=2.0)
async with httpx.AsyncClient(timeout=timeout) as client:
    async with client.stream("GET", "https://api.example.com/export") as response:
        response.raise_for_status()
        async for chunk in response.aiter_bytes():
            consume(chunk)

若 upstream 每 1.5 秒持續送資料,read=2.0 可讓長串流繼續;若某次兩個 chunk 間隔超過兩秒,會觸發 ReadTimeout

3.3 Write timeout

Write timeout 限制等待 request body chunk 寫入網路的時間。小型 JSON request 通常很快完成;大型 upload、慢速對端、壅塞 socket buffer 或 streaming request 更容易遇到。

python
async def body_stream():
    for part in generate_parts():
        yield part

async with httpx.AsyncClient(timeout=timeout) as client:
    response = await client.post(
        "https://api.example.com/import",
        content=body_stream(),
    )

Write timeout 不代表 server 沒收到任何資料;server 可能已收到一部分 body,甚至已根據 headers 建立工作。

3.4 Pool timeout

Pool timeout 限制等待 connection pool 可用容量的時間。這是 client 端排隊,不是 upstream network latency。

python
limits = httpx.Limits(
    max_connections=20,
    max_keepalive_connections=10,
    keepalive_expiry=15.0,
)
timeout = httpx.Timeout(5.0, pool=0.25)

client = httpx.AsyncClient(timeout=timeout, limits=limits)

當 20 個 connection 都在使用中,第 21 個 request 會等待;超過 0.25 秒仍拿不到 capacity 才拋出 PoolTimeout。它通常是 backpressure 訊號:你的呼叫並發度、upstream latency 或 pool size 彼此不匹配。

3.5 Exception hierarchy

text
HTTPError
└── RequestError
    └── TransportError
        └── TimeoutException
            ├── ConnectTimeout
            ├── ReadTimeout
            ├── WriteTimeout
            └── PoolTimeout

不要用同一個 catch 把 timeout、HTTP 5xx 與程式 bug 全部轉成「503」。response.raise_for_status() 產生的是 HTTPStatusError;它代表有收到 HTTP response,和 transport timeout 的觀測語意不同。


4. HTTPX 的預設值與配置方式

HTTPX 官方文件目前說明:預設會在五秒 network inactivity 後拋出 timeout exception。這不是五秒整體 deadline。

4.1 單一 float

python
client = httpx.AsyncClient(timeout=10.0)

這適合簡單工具,但正式服務通常應明確寫出各階段,讓 code review 能看出設計意圖。

4.2 細分四種 timeout

python
timeout = httpx.Timeout(
    timeout=5.0,
    connect=1.0,
    pool=0.2,
)

第一個 timeout=5.0 是未個別指定欄位的預設;connect 與 pool 再覆寫。也可以四個欄位全部明確指定。

4.3 Per-request override

python
async with httpx.AsyncClient(timeout=default_timeout) as client:
    normal = await client.get(normal_url)
    report = await client.get(report_url, timeout=30.0)

override 應是經過分類的 endpoint policy,不應讓每個 call site 任意猜數字。

4.4 timeout=None

python
client = httpx.AsyncClient(timeout=None)

停用 timeout 會讓 stalled I/O 長期占用 connection、task 與上游配額。除非外層有更嚴格且可證明的 deadline,正式服務通常不應全域停用。


5. Overall deadline:如何限制整條工作時間

5.1 為什麼四種 timeout 仍不夠

假設一次業務操作會:

  1. pool wait 0.2 秒。
  2. connect 0.8 秒。
  3. read 4 秒後收到一個 chunk。
  4. 再 read 4 秒收到下一個 chunk。
  5. retry 一次。

每一段都可能未超過局部 timeout,但總時間遠超過 caller 的 latency budget。

5.2 用 asyncio.timeout() 包住完整操作

python
import asyncio
import httpx

async def fetch_with_deadline(client: httpx.AsyncClient, url: str) -> dict:
    try:
        async with asyncio.timeout(3.0):
            response = await client.get(url)
            response.raise_for_status()
            return response.json()
    except TimeoutError as exc:
        raise RuntimeError("overall request deadline exceeded") from exc

外層 deadline 包含 pool、connect、write、read、response parsing,以及你放在 scope 內的 retry delay。內層 HTTPX timeout 仍有價值,因為它能提供更精確的 failure classification。

5.3 傳遞剩餘 budget

在多層 service chain 中,A 不應把自己的 2 秒 deadline 原封不動地給 B。A 還需要 response mapping、logging、DB commit 與回傳時間;應保留安全 margin,將「剩餘 budget」傳給下游。

python
from time import monotonic

def remaining(deadline: float, reserve: float = 0.05) -> float:
    value = deadline - monotonic() - reserve
    if value <= 0:
        raise TimeoutError("no downstream budget remains")
    return value

跨 service 傳遞 deadline 時要明確約定:使用 duration 還是 timestamp、哪個 clock domain、proxy 是否會扣除耗時,以及不可信 client header 是否允許直接控制內部資源。

5.4 Deadline 與 retry budget

如果整體只剩 120ms,就不該開始一個 connect timeout 2 秒的 retry。每次 attempt 前重新計算剩餘時間,並限制最大 attempts。


6. Connection pool:重用、容量與排隊

這是一個 queueing system。Pool 不是「越大越好」的 cache;它同時是 upstream concurrency 的閘門。

6.1 為什麼要重用 Client

HTTPX 官方建議正式程式使用 ClientAsyncClient。同一 client 可以重用 TCP/TLS connection,降低 handshake latency、CPU 與 network round trips。

python
# 反例:hot path 每次建立新 pool
async def bad_fetch(url: str):
    async with httpx.AsyncClient() as client:
        return await client.get(url)

這段語法會正確 close,但在高頻呼叫中無法跨 request 重用 pool。Web service 應在 application lifespan 建立一次,shutdown 時關閉。

6.2 三個 Limits 欄位

欄位 官方預設 意義
max_connections100pool 允許同時存在/使用的 connection 上限
max_keepalive_connections20可保留供重用的 idle keep-alive connections 上限
keepalive_expiry5 秒idle keep-alive connection 保留多久

max_keepalive_connections 不應高於 max_connections。過低會頻繁重建連線;過高會保留更多 socket、TLS state 與 upstream capacity。

6.3 Pool 是 bulkhead,不只是效能設定

Pool limit 可以限制單一 process 對 upstream 的同時壓力。沒有上限的 concurrency 可能在 upstream 變慢時形成正回饋:

text
upstream latency ↑
→ in-flight requests ↑
→ sockets / memory / tasks ↑
→ queueing ↑
→ timeout / retry ↑
→ upstream load 再 ↑

有界 pool 加上短 pool timeout 會較早拒絕,保護本服務和 upstream,但它不是完整 bulkhead:你仍可能需要 per-host semaphore、queue、rate limit 或 circuit breaker。

6.4 粗略容量推理

Little's Law 提供直覺:in_flight ≈ throughput × latency。若單一 worker 對 upstream 峰值 50 requests/s,p95 latency 0.2 秒,平均 in-flight 約 10;還要考慮 burst、p99、streaming 與多 host。

不要直接用平均值配置。實務步驟:

  1. 量測每個 upstream 的 throughput 與 latency distribution。
  2. 設定你願意允許的最大 in-flight。
  3. 乘上 process/worker/pod 數量,確認全域連線數。
  4. 對照 upstream、NAT、load balancer、file descriptor 與 database 的限制。
  5. 壓測 pool wait、拒絕率與 recovery,而不只是成功 latency。

6.5 HTTP/2 的額外維度

HTTP/2 可在一條 connection 上 multiplex 多個 streams,因此「connection 數」不等於「同時 request 數」。但 stream concurrency 仍受 server 設定、flow control 與 transport 實作限制。不要假設開啟 HTTP/2 就不需要 backpressure。


7. FastAPI 中正確管理 AsyncClient 生命週期

7.1 Lifespan 建立與關閉

python
from contextlib import asynccontextmanager

import httpx
from fastapi import FastAPI, Request


@asynccontextmanager
async def lifespan(app: FastAPI):
    timeout = httpx.Timeout(connect=1.0, read=2.0, write=2.0, pool=0.2)
    limits = httpx.Limits(
        max_connections=50,
        max_keepalive_connections=20,
        keepalive_expiry=15.0,
    )
    app.state.http = httpx.AsyncClient(timeout=timeout, limits=limits)
    try:
        yield
    finally:
        await app.state.http.aclose()


app = FastAPI(lifespan=lifespan)


@app.get("/aggregate")
async def aggregate(request: Request):
    response = await request.app.state.http.get("https://api.example.com/data")
    response.raise_for_status()
    return response.json()

7.2 每個 worker 都有自己的 pool

四個 Uvicorn worker 代表四個 process,也就是四個 AsyncClient 與四個 pool。若每個 max_connections=100,理論上的 process-level 合計可能達 400,再乘 pod 數量。

7.3 依 upstream 隔離 client

付款、搜尋、推薦服務的 latency 和重要性不同。使用不同 client/semaphore 可以避免推薦服務塞滿共用 pool,連付款也拿不到 connection。

7.4 不要洩漏 streaming response

使用 manual streaming mode 時,必須確保 response 被關閉,否則 connection 無法回到 pool。

python
async def stream_upstream(client: httpx.AsyncClient, upstream_url: str):
    request = client.build_request("GET", upstream_url)
    response = await client.send(request, stream=True)
    try:
        async for chunk in response.aiter_bytes():
            yield chunk
    finally:
        await response.aclose()

8. Cancellation、disconnect 與遠端 side effect

8.1 本地 task 被取消時

asyncio.timeout() 到期或上游 client disconnect 導致 handler cancellation,本地 await 通常會收到 cancellation。HTTP transport 可能關閉 connection,但遠端是否停止取決於 server、protocol 與它正在做的工作。

8.2 Commit 前後的四種情境

情境 client 觀察 server 狀態 retry 風險
connect 前失敗未建立連線多半未執行較低,但仍要依 operation
request 傳一半WriteTimeout可能收到部分內容protocol dependent
server commit 前慢ReadTimeout可能之後 rollback 或 commit不確定
server commit 後 response 丟失ReadTimeoutside effect 已完成直接 retry 可重複執行

8.3 解法不是把 timeout 調大

對重要寫入,應使用:

  • idempotency key 與 request fingerprint。
  • database unique constraint。
  • 可查詢 operation status。
  • outbox/inbox 或 workflow state。
  • 明確區分「接受工作」與「完成工作」的 asynchronous API。

9. Retry:只有在語意允許時才是韌性

9.1 可重試性來自 operation semantics

通常較安全的候選:GET、HEAD、具 idempotency key 的寫入、明確文件化為 idempotent 的 API。危險候選:沒有 deduplication 的付款、寄信、扣庫存、建立訂單。

9.2 依 exception 分類

  • PoolTimeout:本地已飽和;立即 retry 常讓 queue 更糟。
  • ConnectTimeout:可能是短暫網路問題,也可能是錯誤設定。
  • ReadTimeout:遠端結果未知,寫入尤其危險。
  • HTTP 429/503:可依 Retry-After 與 API contract 處理。
  • HTTP 4xx:多數是永久性 client error,不應盲目 retry。

9.3 Exponential backoff with jitter

python
import asyncio
import random


async def backoff(attempt: int, cap: float = 1.0) -> None:
    upper = min(cap, 0.05 * (2**attempt))
    await asyncio.sleep(random.uniform(0.0, upper))

Jitter 避免大量 clients 同時 timeout 後同步重試。仍須配合最大 attempts 與 overall deadline。

9.4 一個 deadline-aware retry skeleton

python
import asyncio
import httpx
from time import monotonic


async def get_json(client: httpx.AsyncClient, url: str, budget: float) -> dict:
    deadline = monotonic() + budget
    last_error: Exception | None = None

    for attempt in range(3):
        time_left = deadline - monotonic()
        if time_left <= 0:
            break
        try:
            async with asyncio.timeout(time_left):
                response = await client.get(url)
                response.raise_for_status()
                return response.json()
        except (httpx.ConnectTimeout, httpx.ReadTimeout) as exc:
            last_error = exc
            if attempt == 2:
                break
            await backoff(attempt, cap=max(0.0, deadline - monotonic()))

    raise TimeoutError("retry budget exhausted") from last_error

這只是 GET 範例。正式實作還需處理 cancellation、metrics、429/503 policy、response closing 與可測試的 random/clock adapter。


10. Observability:不要只記「request failed」

10.1 最低限度欄位

  • upstream service/host,不要把高 cardinality 完整 URL 當 metric label。
  • method、route template、attempt。
  • exception class:Connect/Read/Write/Pool timeout。
  • overall deadline exceeded 與 local cancellation 分開。
  • elapsed time、remaining budget。
  • pool configuration、worker/pod identity。
  • response status class。

10.2 建議 metrics

text
http_client_requests_total{upstream,method,outcome}
http_client_duration_seconds{upstream,method}
http_client_timeouts_total{upstream,phase}
http_client_pool_wait_seconds{upstream}
http_client_in_flight{upstream}
http_client_retries_total{upstream,reason}

HTTPX 高階 API 不一定直接暴露完整 pool wait timing;可使用 event hooks、transport trace extension、外層 semaphore timing 或 observability integration,但要驗證量測點真正代表什麼。

10.3 Trace 的問題

一條 trace 應回答:caller 的 2 秒去了哪裡?是在自己的 queue、pool wait、connect、server processing、body streaming、retry sleep,還是 response parsing?如果 span 只包整個 client.get(),只能看到症狀,無法定位資源瓶頸。


11. 常見反模式與診斷

11.1 每個 request 建一個 AsyncClient

症狀: handshake 多、latency 抖動、socket churn。 根因: pool 生命週期太短。 修正: application/worker scope client。

11.2 所有 timeout 都設成同一個大數字

症狀: 本地 pool 排隊 30 秒後才失敗。 根因: 沒區分資源等待與 upstream I/O。 修正: pool timeout 通常應短;connect/read/write 依 endpoint SLO 設計,再加 overall deadline。

11.3 timeout=None 再依賴 load balancer

症狀: task、connection 長期懸掛。 根因: 把另一層設備的 timeout 當 application resource policy。 修正: 每層都要有明確且由外向內遞減的 budget。

11.4 PoolTimeout 就加大 pool

症狀: PoolTimeout 暫時下降,但 upstream 更慢或 file descriptors 暴增。 根因: 沒先找 latency、concurrency、泄漏或 retry storm。 修正: 量測 in-flight、pool wait、response closing、upstream capacity 後再調整。

11.5 ReadTimeout 後無條件 retry POST

症狀: duplicate order/charge。 根因: 把 response 未收到誤認為 operation 未執行。 修正: idempotency key、status query、deduplication constraint。


12. Production 設定決策表

問題 先問什麼 典型手段
Connect 慢DNS、TCP、TLS 還是 proxy?短 connect timeout、network telemetry
Read 慢首 byte 還是 chunk gap?server 是否 commit?read timeout、server timeout、idempotency
Pool 飽和concurrency、latency、leak、retry 哪個上升?bounded pool、semaphore、load shedding
總時間超標是否跨多個 attempts/operations?outer deadline、remaining budget
大量 POST retry是否具 dedup contract?idempotency key、operation status
Worker 數增加每 worker pool 是否乘上去?全域 capacity calculation
Streamingresponse 是否一定 close?context manager/finally aclose()

設定值不是教材可以替你的 production 決定的常數。它必須來自 latency SLO、upstream contract、failure cost、worker topology 與壓測。


13. 最小實驗:親手製造四種 timeout

13.1 實驗服務

建立 FastAPI upstream,提供:

  • /delay-headers?seconds=:延遲 response。
  • /stream?gap=:每隔固定時間送 chunk。
  • /consume-slowly:慢速讀 request body。
  • /commit-then-delay:先寫 PostgreSQL 並 commit,再延遲 response。

13.2 PoolTimeout 實驗

python
import asyncio
import httpx


async def main():
    limits = httpx.Limits(max_connections=1, max_keepalive_connections=1)
    timeout = httpx.Timeout(connect=1.0, read=2.0, write=2.0, pool=0.05)

    async with httpx.AsyncClient(limits=limits, timeout=timeout) as client:
        async def call(number: int):
            try:
                await client.get("http://127.0.0.1:8001/delay-headers?seconds=0.5")
                return number, "ok"
            except httpx.PoolTimeout:
                return number, "pool-timeout"

        print(await asyncio.gather(*(call(i) for i in range(5))))


asyncio.run(main())

13.3 必須提交的證據

  1. 表格:scenario、HTTPX exception、總耗時、server 是否收到 request、server 是否 commit。
  2. Pool size 1/5/20 下的 pool wait distribution。
  3. read timeout 和 overall deadline 對慢速 stream 的不同結果。
  4. commit 後 response timeout,證明 client timeout 不會回滾 server transaction。
  5. 有/無 idempotency key retry 的資料庫結果。

13.4 pytest 驗收方向

python
import asyncio
import httpx
import pytest


@pytest.mark.anyio
async def test_pool_timeout_is_classified(upstream_url: str):
    limits = httpx.Limits(max_connections=1)
    timeout = httpx.Timeout(1.0, pool=0.01)
    async with httpx.AsyncClient(limits=limits, timeout=timeout) as client:
        first = client.get(f"{upstream_url}/delay-headers?seconds=0.3")
        second = client.get(f"{upstream_url}/delay-headers?seconds=0.3")
        results = await asyncio.gather(first, second, return_exceptions=True)
        assert any(isinstance(item, httpx.PoolTimeout) for item in results)

測試必須在可控的本機 upstream 執行,避免依賴公共網站 latency。


14. 自我檢查

Q1. `read=5` 是否代表整個 response 五秒內完成?

不是。它限制等待 response data chunk 的時間;持續有 chunk 的長串流可超過五秒。

Q2. PoolTimeout 是否表示 upstream TCP connect 太慢?

不是。它發生在 client 等待 pool capacity;request 可能尚未開始 connect。

Q3. ReadTimeout 後可以斷定 server 沒有 commit 嗎?

不可以。server 可能已完成 side effect,只是 response 遲到或遺失。

Q4. 為什麼不能在 hot loop 中反覆建立 AsyncClient?

每個 client 有自己的 connection pool;生命週期過短會失去 TCP/TLS 重用並製造 socket churn。

Q5. `max_connections=100` 在四 workers、五 pods 時代表什麼?

這通常是每個 client/process 的限制,理論合計可能達 100 × 4 × 5;仍要考慮 per-origin 與 HTTP/2 行為。

Q6. 為什麼 PoolTimeout 後立即 retry 可能更糟?

它通常表示本地 queue 已飽和;retry 增加競爭與排隊,可能形成 retry storm。

Q7. Timeout 與 deadline 最核心差異?

timeout 通常限制一個局部等待;deadline 限制完整操作在某個最晚時間前完成。

Q8. 哪些 timeout 可以安全 retry?

不能只看 exception class。必須同時看 method/operation semantics、idempotency、剩餘 budget 與 upstream contract。

Q9. Streaming response 最容易造成哪種 pool 問題?

未完整消費又未 close response,connection 無法歸還 pool,最終造成 pool starvation。

Q10. HTTP/2 是否讓 connection limit 無關緊要?

否。它能 multiplex streams,但仍有 server stream limit、flow control、socket 與應用層 backpressure。


15. 官方來源

閱讀官方文件時,持續問三個問題:這個限制量測哪一段?它是 inactivity timeout 還是 overall deadline?超時後遠端 side effect 的狀態是否可知?