# How Redis Enables Horizontal Scaling in Kaneo's Real-Time Architecture

> Discover how Redis powers horizontal scaling in Kaneo's real-time architecture by acting as the Pub/Sub message bus for WebSocket event synchronization across API instances.

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

---

**Redis acts as the Pub/Sub message bus that synchronizes WebSocket events across multiple Kaneo API instances, replacing the default in-memory adapter when horizontal scaling is required.**

The open-source project management platform [Kaneo](https://github.com/usekaneo/kaneo) uses Redis to coordinate real-time updates across distributed API containers. While single-instance deployments rely on an in-memory broadcast adapter, production environments with multiple replicas require Redis to ensure consistent event delivery. Understanding the role of Redis in Kaneo's real-time architecture is critical for achieving fault-tolerant, horizontally scalable deployments.

## The Single-Instance Limitation

By default, each Kaneo API instance runs an **in-memory broadcast adapter** that delivers WebSocket events only to sockets created within that specific process. This implementation, found in [`apps/api/src/ws/in-memory-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/in-memory-broadcast-adapter.ts), functions correctly for development or single-container deployments. However, when running multiple API pods behind a load balancer, this approach fails—events emitted on Instance A never reach clients connected to Instance B, causing state fragmentation across your cluster.

## How the Redis Broadcast Adapter Works

When Redis configuration is detected, Kaneo swaps the in-memory adapter for the **Redis-backed broadcast adapter** (`RedisBroadcastAdapter`). This adapter, implemented in [`apps/api/src/ws/redis-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/redis-broadcast-adapter.ts), transforms Redis into a central message hub for all WebSocket communications.

### The Pub/Sub Mechanism

The adapter publishes every WebSocket message to a Redis Pub/Sub channel, while every API instance subscribes to that same channel. When a client triggers an event on any instance, the following occurs:

1. The controller calls `broadcastAdapter.broadcastToProject()`
2. The `RedisBroadcastAdapter` serializes the message and publishes it to `project:${projectId}`
3. All subscribed API instances receive the message via their Redis subscriber connections
4. Each instance forwards the event to its locally connected WebSocket clients

This guarantees that every connected client receives identical updates regardless of which API container handled the original HTTP request.

### Fallback Behavior

If no Redis configuration is present, Kaneo automatically falls back to the in-memory adapter. The selection logic in [`apps/api/src/ws/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/index.ts) demonstrates this conditional instantiation:

```typescript
// apps/api/src/ws/index.ts
import { isRedisConfigured } from "../redis";
import { RedisBroadcastAdapter } from "./redis-broadcast-adapter";
import { InMemoryBroadcastAdapter } from "./in-memory-broadcast-adapter";

export const broadcastAdapter = isRedisConfigured()
  ? new RedisBroadcastAdapter()
  : new InMemoryBroadcastAdapter();

```

The `isRedisConfigured()` function, defined in [`apps/api/src/redis/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/redis/index.ts), checks for environment variables such as `REDIS_URL` to determine which adapter to instantiate.

## Configuring Redis for Production

Kaneo supports multiple Redis deployment topologies through environment variables documented in [`ENVIRONMENT_SETUP.md`](https://github.com/usekaneo/kaneo/blob/main/ENVIRONMENT_SETUP.md). The configuration determines how the `getRedisPub()` and subscriber clients in [`apps/api/src/redis/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/redis/index.ts) establish connections.

### Environment Variables

Enable Redis mode by setting the following variables:

```bash

# .env

REDIS_URL=redis://redis-master:6379
REDIS_PASSWORD=yourRedisPassword
REDIS_SENTINEL_PASSWORD=yourSentinelPassword  # Required only for Sentinel mode

```

### Supported Deployment Modes

Kaneo's Redis integration accommodates three production topologies:

- **Standalone** – Single Redis instance for small-to-medium deployments
- **Sentinel** – High availability with automatic failover using Redis Sentinel
- **Cluster** – Horizontal sharding for massive scale-out scenarios

The connection handling in [`apps/api/src/redis/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/redis/index.ts) automatically configures the appropriate connection strategy based on the provided `REDIS_URL` format and sentinel-specific variables.

## Implementation Deep Dive

When a task updates, controllers utilize the broadcast adapter to notify project members. The following example from [`apps/api/src/controllers/task.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/controllers/task.ts) demonstrates publishing via the adapter interface:

```typescript
// apps/api/src/controllers/task.ts
import { broadcastAdapter } from "../../ws";

export async function updateTask(c) {
  // …perform database update…
  await broadcastAdapter.broadcastToProject(projectId, {
    type: "task.updated",
    payload: { taskId, changes },
  });
}

```

The `RedisBroadcastAdapter` implements the `BroadcastAdapter` interface by publishing JSON-serialized messages to Redis channels:

```typescript
// apps/api/src/ws/redis-broadcast-adapter.ts
export class RedisBroadcastAdapter implements BroadcastAdapter {
  async broadcastToProject(projectId: string, msg: WSMessage) {
    const pub = getRedisPub();               // from apps/api/src/redis/index.ts
    await pub.publish(`project:${projectId}`, JSON.stringify(msg));
  }
}

```

## Summary

- **Redis enables horizontal scaling** by acting as a Pub/Sub hub that synchronizes WebSocket events across all Kaneo API instances.
- **Automatic fallback** occurs when Redis is unavailable or unconfigured, switching to the in-memory adapter for single-instance operation.
- **Flexible deployment** supports standalone, Sentinel, and Cluster Redis configurations through environment variables in [`apps/api/src/redis/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/redis/index.ts).
- **Consistent state** is maintained across distributed containers via the `RedisBroadcastAdapter` publishing to `project:${projectId}` channels.

## Frequently Asked Questions

### What happens if the Redis connection fails during runtime?

According to the test suite in [`tests/api/ws/user-broadcast-redis-failure.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api/ws/user-broadcast-redis-failure.test.ts), Kaneo gracefully degrades to the in-memory adapter behavior when Redis becomes unavailable. While this prevents cross-instance communication, it maintains local WebSocket functionality rather than crashing the API container.

### Can I run Kaneo in production without Redis?

Yes, but only if you deploy a single API instance. Without Redis configured, the `InMemoryBroadcastAdapter` restricts real-time updates to sockets within that specific process. Multiple containers without Redis will result in inconsistent real-time state across your user base.

### Which Redis commands does Kaneo use for real-time messaging?

Kaneo utilizes the standard **PUBLISH** and **SUBSCRIBE** commands. The `RedisBroadcastAdapter` calls `pub.publish()` to emit messages to channel names formatted as `project:${projectId}`, while each instance maintains a subscriber connection listening for these specific project channels.

### How does Kaneo handle message serialization for Redis?

All WebSocket messages undergo `JSON.stringify()` serialization before transmission to Redis, as implemented in [`apps/api/src/ws/redis-broadcast-adapter.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/ws/redis-broadcast-adapter.ts). The subscriber instances parse these messages and forward them to local WebSocket connections, maintaining type consistency through the `WSMessage` interface.