Connection Pooling
The HTTP adapter reuses TCP/TLS connections through a process-wide agent pool. The HTTP/2 adapter has its own session pool. Both are on by default; you do not have to configure anything to benefit from them.
How It Works (HTTP/1.1)
- The first request to an origin opens a fresh socket and (for HTTPS) negotiates TLS.
- After the response completes, the socket is not closed — it is parked in the pool.
- Subsequent requests to the same origin reuse the parked socket, skipping DNS, the TCP handshake, and the TLS handshake entirely.
- Sockets idle in the pool past their keep-alive window are closed automatically.
import rezo from 'rezo';
// First request opens a connection.
await rezo.get('https://api.example.com/users');
// Second request reuses the same socket — no DNS, TCP, or TLS work.
await rezo.get('https://api.example.com/posts'); You can confirm reuse from any of these signals:
getSocketTelemetry(socket).reuse.isReused === true(see Socket Telemetry)response.config.timing.domainLookupStart === response.config.timing.domainLookupEndresponse.config.timing.connectStart === response.config.timing.connectEnd
Per-Request Keep-Alive
Per-request Rezo options that affect pooling:
| Option | Type | Default | What it does |
|---|---|---|---|
keepAlive | boolean | false | Keep the underlying TCP connection open after the response so the next call to the same host reuses it. |
keepAliveMsecs | number | 60000 (1 min) | How long an idle socket lives before it is closed. Only meaningful when keepAlive: true. |
const client = rezo.create({
keepAlive: true, // reuse sockets for subsequent requests
keepAliveMsecs: 30_000 // close idle sockets after 30s
});
await client.get('https://api.example.com/users');
await client.get('https://api.example.com/posts'); // reuses the socket
keepAlive: falseis the default because it makes short-lived scripts exit immediately. If you flip it totrue, the Rezo agent pool setssocket.unref()on idle sockets so Node can still exit cleanly when there’s no other work pending — you don’t need a manualclient.destroy()call just to shut down.
The Agent Pool (HTTP/1.1)
Rezo manages http.Agent and https.Agent instances internally. The pool’s defaults match Node’s recommended values:
| Setting | Default | Notes |
|---|---|---|
maxSockets | 256 | Max concurrent sockets per origin |
maxFreeSockets | 64 | Max idle sockets parked per origin |
scheduling | 'lifo' | Reuse the most-recently-freed socket; lets old sockets close naturally and minimizes memory |
These are not exposed as top-level Rezo options today. To override them, build your own agent and pass it via httpAgent / httpsAgent:
import { Agent as HttpAgent } from 'node:http';
import { Agent as HttpsAgent } from 'node:https';
const httpsAgent = new HttpsAgent({
keepAlive: true,
keepAliveMsecs: 60_000,
maxSockets: 64,
maxFreeSockets: 16,
scheduling: 'fifo'
});
const client = rezo.create({
httpsAgent,
httpAgent: new HttpAgent({ keepAlive: true, keepAliveMsecs: 60_000 })
}); Custom agents are passed straight through to http.request() / https.request(), so anything Node accepts is fair game.
Separate Pools for Different Configurations
Requests with different connection settings (custom CA, custom local address, proxy, mTLS client cert, …) automatically end up on different pool buckets. Configuration differences never share sockets, which is what you want for security and correctness.
DNS Caching
Connection pooling reuses an established socket; DNS caching removes the lookup entirely on a fresh connection. They’re complementary.
DNS caching is configured on the instance via cache.dns:
const client = rezo.create({
cache: {
dns: true // 1-minute TTL, 1000 entries (defaults)
}
});
// or with custom settings:
const client2 = rezo.create({
cache: {
dns: { ttl: 300_000, maxEntries: 5_000 }
}
}); To enable DNS caching for a single request only, use the per-request dnsCache option:
await rezo.get('https://api.example.com/data', {
dnsCache: { ttl: 300_000 }
}); See DNS Cache for the full configuration surface, the global singleton, and invalidation.
HTTP/2 Session Pool
HTTP/2 doesn’t pool sockets the way HTTP/1.1 does — it pools sessions. A single HTTP/2 session multiplexes many requests over one TCP/TLS connection.
The pool runs as a singleton (Http2SessionPool.getInstance()):
- Idle eviction — a session with zero in-flight streams is closed after 60 s of inactivity.
- GOAWAY tracking — a session that has received a GOAWAY frame is marked unhealthy; the next request opens a fresh session.
- Reference counting — eviction never tears down a session that has live streams.
You don’t normally interact with the pool, but you can pull it for diagnostics:
import { Http2SessionPool } from 'rezo/adapters/http2';
const pool = Http2SessionPool.getInstance(); See HTTP/2 Sessions for the full lifecycle.
When to Tune
For the vast majority of applications, leave the defaults alone. Tune when:
- High concurrency, single host — bump
maxSockets(via a customhttpsAgent) above 256 if you reliably need more parallel sockets per origin. - Memory-constrained servers — drop
maxFreeSocketsto limit idle socket residency. - Long-running daemons / scrapers — consider
keepAlive: truepluskeepAliveMsecsaligned to your traffic gaps. - CLI tools / one-shot scripts — keep
keepAlive: false(the default) so the process exits as soon as the request finishes.