Advanced

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)

  1. The first request to an origin opens a fresh socket and (for HTTPS) negotiates TLS.
  2. After the response completes, the socket is not closed — it is parked in the pool.
  3. Subsequent requests to the same origin reuse the parked socket, skipping DNS, the TCP handshake, and the TLS handshake entirely.
  4. 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.domainLookupEnd
  • response.config.timing.connectStart === response.config.timing.connectEnd

Per-Request Keep-Alive

Per-request Rezo options that affect pooling:

OptionTypeDefaultWhat it does
keepAlivebooleanfalseKeep the underlying TCP connection open after the response so the next call to the same host reuses it.
keepAliveMsecsnumber60000 (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: false is the default because it makes short-lived scripts exit immediately. If you flip it to true, the Rezo agent pool sets socket.unref() on idle sockets so Node can still exit cleanly when there’s no other work pending — you don’t need a manual client.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:

SettingDefaultNotes
maxSockets256Max concurrent sockets per origin
maxFreeSockets64Max 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 custom httpsAgent) above 256 if you reliably need more parallel sockets per origin.
  • Memory-constrained servers — drop maxFreeSockets to limit idle socket residency.
  • Long-running daemons / scrapers — consider keepAlive: true plus keepAliveMsecs aligned to your traffic gaps.
  • CLI tools / one-shot scripts — keep keepAlive: false (the default) so the process exits as soon as the request finishes.