Edit

Stream at Scale

BetaTelefunc 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 Channel ends.

Sanity check: after deploying behind a sticky load balancer, open the browser network tab, refresh once, and confirm every request to /_telefunc carries 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;
}
backend telefunc
  cookie SERVERID insert indirect nocache
  server app1 app1:3000 check cookie app1
  server app2 app2:3000 check cookie app2
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 BroadcastTransport interface 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 scale option instead of a load balancer.

See also