Stream at Scale
Beta — Telefunc Stream is in beta: breaking changes may occur in any version update.
If you use Telefunc Stream, scaling Telefunc horizontally (multiple Node instances, multiple containers, multiple machines) adds two requirements:
- Sticky sessions (so a reconnecting client returns to the same server instance) — your load balancer must route every request from a given client to the same instance.
Required: every stream needs this.
- Cross-instance broadcast transport (a broadcast must reach subscribers on every server instance) — install one such as
@telefunc/redis.Optional: this is required only for streams that broadcast.
Sticky sessions
A Channel is a stateful connection. Its server-side state — the new Channel() instance, its send()/listen() closures, any interval the telefunction set up — lives in one server process. When the client reconnects (network issues, a page reload, an SSE→WS upgrade), the next request has to land on the same process or the channel can't recover.
This is the same constraint Socket.IO documents under Using multiple nodes. The same load-balancer feature solves the problem for Telefunc: a sticky session, usually backed by a cookie or by the client IP.
Without sticky sessions, a reconnect that lands on a different instance sees no matching channel state, the recovery handshake fails, and the client's
Channelends.
Sanity check: after deploying behind a sticky load balancer, open the browser network tab, refresh once, and confirm every request to
/_telefunccarries the same sticky cookie. If two consecutive requests carry different sticky values, the load balancer isn't configured for sticky sessions.
Nginx — ip_hash
upstream telefunc {
ip_hash;
server app1:3000;
server app2:3000;
}HAProxy — cookie
backend telefunc
cookie SERVERID insert indirect nocache
server app1 app1:3000 check cookie app1
server app2 app2:3000 check cookie app2
Caddy — lb_policy cookie
reverse_proxy app1:3000 app2:3000 {
lb_policy cookie
}
AWS ALB — target-group stickiness
In the target group's attributes, enable Stickiness with type Load balancer generated cookie.
Cloud Run / serverless
Serverless platforms that don't expose sticky-session routing aren't a good fit for Channel. Broadcasts still work (each publish round-trips through Redis), but channel reconnects fail whenever they land on a different instance. Most teams pair Telefunc with a long-running server tier when they need channels at scale.
Broadcast
Broadcast is different: publishers and subscribers are intentionally decoupled — they only share a key string. Each instance keeps its own subscriber list locally, and the broadcast transport is what makes a publisher on instance A reach a subscriber on instance B.
In a single-instance setup, the default in-memory transport is enough. In a multi-instance setup, install a transport that fans out across the cluster — such as @telefunc/redis.
Redis is the only adapter shipped today, but the
BroadcastTransportinterface is small (about four methods), so you can write a custom transport on top of NATS, Kafka, RabbitMQ, or any other message broker.
For Cloudflare Workers, Telefunc has an adapter that uses Durable Objects — see Stream on Cloudflare.
BroadcastTransport
For other backends (NATS, Kafka, …), implement BroadcastTransport and assign it to config.broadcast.transport. Telefunc wraps it with subscriber multiplexing and same-node delivery, so each key only opens one upstream subscription no matter how many local subscribers attach.
type BroadcastTransport = {
send(key: string, payload: string): { seq: number; timestamp: number } | Promise<{ seq: number; timestamp: number }>
listen(key: string, onMessage: (payload: string, info: { seq: number; timestamp: number }) => void): () => void
sendBinary(key: string, payload: Uint8Array): { seq: number; timestamp: number } | Promise<{ seq: number; timestamp: number }>
listenBinary(key: string, onMessage: (payload: Uint8Array, info: { seq: number; timestamp: number }) => void): () => void
}send() / sendBinary() must return the assigned { seq, timestamp } so subscribers across nodes see a single global order per key. listen() / listenBinary() return an unsubscribe function and are called at most once per key.
On Cloudflare Workers, Telefunc handles distributed broadcast automatically via Durable Objects — no transport needed. See Stream on Cloudflare.
Cloudflare
Cloudflare Workers takes a different approach: telefunc/cloudflare routes channels through Durable Objects instead of sticky sessions, and fans out broadcasts across regions automatically. As a result, neither a sticky load balancer nor a broadcast transport is needed. See:
You scale with the
scaleoption instead of a load balancer.