# How to Scale Kaneo's WebSocket Broadcasting Horizontally

> Scale Kaneo's WebSocket broadcasting horizontally by configuring RedisBroadcastAdapter with Redis URL. Synchronize real-time events across multiple API instances using Redis Pub/Sub for enhanced performance.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: performance
- Published: 2026-08-30

---

**To scale Kaneo's WebSocket broadcasting horizontally, configure the `REDIS_URL` environment variable to enable `RedisBroadcastAdapter`, which synchronizes real-time events across multiple API instances using Redis Pub/Sub.**

Kaneo's real-time collaboration features rely on WebSocket connections to deliver live updates to connected clients. While the default implementation works for single-instance deployments, production workloads require horizontal scaling to handle high concurrency. The solution leverages a pluggable **broadcast adapter** architecture that replaces in-memory messaging with Redis-backed message distribution.

## Understanding Kaneo's Broadcast Adapter Architecture

The WebSocket layer in `apps/api/src/ws/` implements a clean adapter pattern defined in [`broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/broadcast-adapter.ts). This interface abstracts the underlying message transport, allowing the system to switch between single-instance and distributed modes without changing application code.

**InMemoryBroadcastAdapter** serves as the default fallback when no external message broker is configured. This adapter maintains WebSocket connections in local memory and broadcasts events only to clients connected to the same process instance. While efficient for development or low-traffic deployments, it cannot propagate messages across multiple API replicas.

**RedisBroadcastAdapter** (located in [`redis-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/redis-broadcast-adapter.ts)) solves this limitation by integrating with Redis Pub/Sub. When enabled, each API instance publishes messages to shared Redis channels while simultaneously subscribing to messages from peer instances. This ensures that a broadcast initiated on one server reaches WebSocket clients connected to any other server in the cluster.

## How Redis Pub/Sub Enables Horizontal Scaling

The `RedisBroadcastAdapter` implements a sophisticated message routing protocol that guarantees exactly-once delivery per client while maintaining low latency.

### Instance Identification and Channel Structure

Each API instance generates a unique **instance ID** using `randomUUID()` during startup. The adapter subscribes to two distinct channel patterns in Redis:

- `kaneo:ws-project:<project-id>:broadcast` – for project-scoped collaboration events
- `kaneo:ws-user:<user-id>:broadcast` – for user-specific notifications

When your application calls `broadcastToProject(projectId, payload)` or `broadcastToUser(userId, payload)`, the adapter executes a two-phase delivery:

1. **Local delivery**: The message transmits immediately to WebSocket connections existing on the current instance.
2. **Global propagation**: The adapter publishes the payload to the appropriate Redis channel, prefixed with the origin instance ID.

### Duplicate Prevention Mechanism

Subscribing instances receive all Pub/Sub messages but implement origin filtering to prevent echo effects. The adapter compares the `origin` field in each incoming message against its own instance ID. Messages originating from the local instance are discarded, while external messages are forwarded to local WebSocket clients. This de-duplication logic ensures clients receive exactly one copy of each event regardless of cluster topology.

## Step-by-Step Configuration Guide

Follow these steps to enable horizontal WebSocket broadcasting in your Kaneo deployment:

1. **Provision Redis infrastructure**: Deploy a Redis instance (version 6.0 or higher recommended) using Docker, managed cloud services, or a Redis Cluster for high availability. Ensure network connectivity between your API instances and the Redis host.

2. **Configure environment variables**: Set `REDIS_URL` to your Redis connection string (e.g., `redis://redis:6379`). Alternatively, use `REDIS_HOST` and `REDIS_PORT` depending on your deployment configuration. The function `isRedisConfigured()` in the source code detects these variables to instantiate `RedisBroadcastAdapter` instead of the in-memory version.

3. **Deploy multiple API replicas**: Launch multiple containers or pods running `ghcr.io/usekaneo/kaneo-api:latest` behind a load balancer. Each instance calls `initializeWebSocketAdapter()` during startup (invoked from the Hono application initialization in [`apps/api/src/ws/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/index.ts)), automatically entering clustered mode.

4. **Tune broadcast latency**: Optionally adjust the internal debounce timeout (default 100ms) in [`redis-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/redis-broadcast-adapter.ts) to balance between message latency and Redis write volume based on your traffic patterns.

## Source Code Implementation Details

The WebSocket initialization logic resides in [`apps/api/src/ws/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/index.ts), which orchestrates connection tracking and adapter selection:

```typescript
// apps/api/src/ws/index.ts
await initializeWebSocketAdapter();   // Automatically selects RedisBroadcastAdapter when REDIS_URL is set

```

When `REDIS_URL` is present, the system instantiates `RedisBroadcastAdapter` which implements the interface defined in [`broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/broadcast-adapter.ts). Your application code requires no modifications when switching adapters:

```typescript
import { broadcastToUser } from '@kaneo/api/src/ws';

// This call works identically in single-instance or clustered modes
broadcastToUser('user-123', { type: 'NOTIFICATION_CREATED', data: { id: 456 } });

```

The underlying implementation ensures that when this executes on any API replica, the `RedisBroadcastAdapter` publishes to `kaneo:ws-user:user-123:broadcast`, and all other replicas deliver the payload to their local connections for that user.

For Docker Compose deployments, add the Redis service and environment configuration:

```yaml
services:
  api:
    image: ghcr.io/usekaneo/kaneo-api:latest
    environment:
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
    deploy:
      replicas: 3
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

```

## Summary

- **Broadcast Adapter Pattern**: Kaneo uses [`broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/broadcast-adapter.ts) to abstract message transport, supporting seamless switching between `InMemoryBroadcastAdapter` and `RedisBroadcastAdapter`.
- **Redis Pub/Sub**: Horizontal scaling requires `REDIS_URL` configuration to enable cross-instance message propagation via `kaneo:ws-project` and `kaneo:ws-user` channels.
- **Duplicate Filtering**: Each instance uses a unique UUID to filter its own messages from Redis subscriptions, ensuring exactly-once delivery to WebSocket clients.
- **Zero Code Changes**: Existing calls to `broadcastToProject` and `broadcastToUser` function identically in clustered configurations without application modifications.
- **Key Files**: [`broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/broadcast-adapter.ts) (interface), [`redis-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/redis-broadcast-adapter.ts) (Redis implementation), [`in-memory-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/in-memory-broadcast-adapter.ts) (fallback), and [`index.ts`](https://github.com/usekaneo/kaneo/blob/main/index.ts) (initialization).

## Frequently Asked Questions

### What is the difference between InMemoryBroadcastAdapter and RedisBroadcastAdapter?

`InMemoryBroadcastAdapter` stores WebSocket connections in process memory and broadcasts only to local clients, suitable for single-instance deployments. `RedisBroadcastAdapter` uses Redis Pub/Sub to synchronize messages across all API instances, enabling horizontal scaling but requiring an external Redis service.

### How does Kaneo prevent duplicate WebSocket messages when using multiple instances?

The `RedisBroadcastAdapter` attaches a unique instance ID (generated via `randomUUID()`) to every published message. When instances receive messages from Redis, they compare the `origin` field against their own ID and discard messages that originated locally, forwarding only external messages to connected clients.

### What Redis version or configuration does Kaneo require?

Kaneo requires Redis 6.0 or higher with Pub/Sub functionality enabled. Set the `REDIS_URL` environment variable to a standard Redis connection string. For production deployments, use Redis persistence or a managed service; clustering is supported as long as Pub/Sub commands are available to all API instances.

### Do I need to modify my application code to support horizontal WebSocket scaling?

No. The broadcast adapter pattern abstracts the transport layer. Your existing code using `broadcastToUser` or `broadcastToProject` functions identically regardless of whether `InMemoryBroadcastAdapter` or `RedisBroadcastAdapter` is active. Simply setting `REDIS_URL` enables horizontal scaling without code changes.