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

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 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, 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, 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 demonstrates this conditional instantiation:

// 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, 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. The configuration determines how the getRedisPub() and subscriber clients in apps/api/src/redis/index.ts establish connections.

Environment Variables

Enable Redis mode by setting the following variables:


# .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 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 demonstrates publishing via the adapter interface:

// 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:

// 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.
  • 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, 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. The subscriber instances parse these messages and forward them to local WebSocket connections, maintaining type consistency through the WSMessage interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →