Reference

Scale & Sticky Sessions

← Back to System Internals Reference

The session server is a horizontally scaled pool of instances behind a load balancer, with a bounded number of concurrent avatar "agent slots" per instance. Three mechanisms make this work for a client: sticky routing, a capacity queue, and externalized session state.

Sticky routing: stickyId

The single most important scaling detail. A live avatar session is stateful and pinned to one server instance (it owns the ASR peer connection, the speech pipeline, and the brain conversation). Socket.IO starts on HTTP long-polling and only later upgrades to WebSocket. Those initial polling requests must all reach the same instance, or the handshake breaks.

The STV video channel does not need stickiness. It's a stateless WHEP stream, scaled independently of the control plane.

Capacity & the queue (throwToNoAgent / throwToExceededTier)

Each instance has a bounded number of agent slots (the face-renderer + brain pipeline is expensive). Two distinct "full" signals:

Signal Meaning Client behavior
throwToNoAgent All agent slots currently busy (transient) Enter availability queue (poll until a slot frees)
throwToExceededTier Account plan/tier limit hit (hard) Fail immediately: TIER_EXCEEDED, not recoverable

The queue (transient capacity):

Session validity is checked separately via isValidSession → validSession / throwToExceededTier / throwToBadRequest.

Connection recovery vs. session recovery

Scenario What happens
Transport blip Socket.IO's built-in connectionStateRecovery handles it: if socket.active on disconnect, it auto-reconnects with exponential backoff and jitter, and may restore the same socket (socket.recovered === true). A short blip doesn't tear down the avatar.
Recoverable transport drop (within ~20s) The server preserves the session on the same instance: the live STV/ASR session and in-memory state survive, and a re-join is not needed. This SDK's KalturaAvatarSession relies on this. It rides the recovery, emits reconnecting/reconnected, and does not re-join.
Permanent disconnect (socket.active === false, or past the recovery window) The session is gone. The avatar reconnects fresh: KalturaAvatarSession's cold reconnect rebuilds the socket, re-joins, and re-creates the STV/agent session with a new agent slot. It reuses the same stickyId, so it lands back on the instance that stickyId maps to, not a new random one. Same-instance resume exists (row above). Cross-instance resume does not: only the brain thread is resumable via threadId.

Distinct timeouts pinpoint where the connection broke: HANDSHAKE_TIMEOUT (transport up, server silent, so the queue activates) vs CONNECTION_TIMEOUT (transport never came up).

Externalized state

Slot accounting isn't per-instance guesswork: checkAvailability returns the same capacity picture no matter which instance answers it. A slot is available when STV has free capacity (unless the call is speech-only) AND the ASR service is available AND activeCalls < maxCalls. availabilityResult.details surfaces exactly these: {stvAvailable, whisperAvailable, activeCalls, maxCalls, capacityAvailable}. The brain conversation/thread state is also externalized: the same thread is resumable via threadId regardless of which instance handles a later turn over the text API.

For what a custom (no-Kaltura-lib) client must implement to work correctly with this scaling model, see Minimal Reimplementation Recipe's "Implications for a Custom Client".

Doc Covers
System Internals Reference · Connection and Handshake The connect sequence this queue sits alongside
System Internals Reference · Resilience and Failure Handling The failure-mode matrix that references throwToNoAgent/throwToExceededTier
System Internals Reference Back to the index
Click to talk with Nova — she knows this whole SDK.
Nova AI assistant — knows this whole site

Reloading starts a fresh chat. “New conversation” does the same without leaving the drawer.