We had a service making many outbound HTTP calls with bare requests.get() and requests.post() calls.
That is fine for a script. It is less ideal for a service that keeps calling the same hosts. If every call creates a fresh connection path, the request may spend time on connection setup before doing the actual work.
The fix was simple: use a shared HTTP session.
The problem
A new outbound HTTPS request may need to set up a TCP connection and negotiate TLS before the server can process the request. For one call, that may not matter much. Across many small calls to the same host, it adds up.
In the source note, this pattern showed up across roughly 75 outbound HTTP calls in 28 files. The fix was not a new cache or a new service. We just needed to reuse connections properly.
The fix
In requests, connection reuse comes from requests.Session.
Create one session at module level and route repeated outbound calls through it:
import requests
from requests.adapters import HTTPAdapter
http_session = requests.Session()
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=10)
http_session.mount("http://", adapter)
http_session.mount("https://", adapter)
response = http_session.post(
"https://api.example.com/sign",
json={"path": path},
timeout=10,
)
The important part is not the exact pool size. The important part is that the same warm process can reuse existing connections to the same host.
This matters in serverless too. A cold start still starts fresh, but a warm Lambda instance can keep the module-level session around for later invocations.
What changed
The improvement showed up most clearly on small repeated calls, where connection setup was a meaningful part of total latency.
| Metric | Before | After | Delta |
|---|---|---|---|
| Average | 107 ms | 85 ms | -22 ms (-21%) |
| p50 | 106 ms | 85 ms | -21 ms (-20%) |
| Minimum | 90 ms | 40 ms | -50 ms (-56%) |
The exact numbers will vary by network, upstream, and workload. The useful takeaway is simpler: if the service repeatedly calls the same host, a shared session can remove avoidable setup work.
When this helps
This is most useful when:
- the same process calls the same host repeatedly
- the calls are small enough that setup time is visible
- the upstream supports keep-alive
- the process stays warm long enough to reuse the connection
It helps less when most latency comes from downloads, database work, model calls, cold starts, or slow upstream processing.
The lesson
Do not create a fresh HTTP client path for every repeated outbound call.
Use a shared session, set timeouts, and benchmark the call path where connection setup is visible. The gain may be small per request, but it adds up when a service makes many small outbound calls to the same hosts.