TCP
One multiplexed, framed connection per peer instead of one HTTP connection per in-flight request. On Node, this is the recommended host-to-host transport.
Installation
pnpm add @sigx/actors-tcpimport { cluster, httpTransport } from '@sigx/actors/cluster';
import { tcpTransport } from '@sigx/actors-tcp';
cluster({
providers,
advertise: 'http://10.0.4.7:7311',
secret: process.env.HOST_SECRET,
// A CHAIN: TCP wherever the peer advertises it, HTTP everywhere else.
// That is what makes a rolling deploy of this transport possible.
transport: [tcpTransport({ port: 11111 }), httpTransport()],
});
Why you would use it
On Node this is the transport hosts should run on — for latency and CPU per hop as much as for socket count.
Per hop, one machine, five runs, against HTTP with its pool tuned to the concurrency:
| Sockets | calls/s | p99 per call | |
|---|---|---|---|
| tuned HTTP | 65 | ~15.6k | 10–13 ms |
| TCP | 3 | ~91k | 1.3 ms |
Across a sixteen-host fleet under an engine-bound workflow load, TCP completed 1.4–1.9× the runs per second past 200 offered, with start-latency p99 under 200 ms where HTTP's was over 100 s — HTTP's per-peer fetch pool saturated, calls timed out, and retries duplicated directory claims (10,184 dispatch retries against 16; 16,024 claim conflicts against 16).
Socket count is the other reason. HTTP's pool sizes to concurrency × peers: roughly
12,600 sockets per host at concurrency 64 across 99 peers — file descriptors, kernel buffers,
conntrack entries and a connection burst on every peer restart. One connection per peer does
not change with RTT.
The full numbers are on Host transports.
HTTP remains the default
And must: @sigx/actors/cluster stays zero-dependency and WinterCG-clean so Cloudflare
Workers keep working, and HTTP is the only transport that runs everywhere. It stays the client
endpoint and the fallback the chain drops to during a rolling deploy.
This package is Node-only by design — it uses node:net.
Deploying it
HostDescriptor.addresses carries a tcp entry per host, so a mixed cluster is expressible
and the rollout is safe:
- Deploy with
transport: [tcpTransport(), httpTransport()]everywhere. - Hosts on the new build advertise
tcpand use it with each other; hosts that do not are still reached over HTTP. - Once every host advertises
tcp, drophttpTransport()from the chain if you want the internal HTTP mount gone entirely.
When a connection drops
A unary call already on the wire when its connection drops rejects with an ordinary Error —
[sigx actors] host connection closed with this call in flight (<reason>): outcome unknown, not retried —
never unreachable, because the owner may have run it. Re-issue it only if the method is
idempotent; see when a call is retried.
Streams and watches still fail unreachable before their first chunk.
Two hosts dialling each other at once keep both sides' in-flight calls: the losing socket
drains and closes once both ends are done. handshakeTimeoutMs bounds an outbound connect as
well as an inbound handshake.
Next steps
- Installation — options and
advertiseHost. - API reference — exports.
- Host transports — the full comparison.
