Client Cluster Mode

TopGun clients can connect to a multi-node cluster for high availability and horizontal scalability. The client automatically handles node discovery, partition-aware routing, and failover.

Overview

Multi-Node

Connect to multiple seed nodes for redundancy

Smart Routing

Operations route directly to partition owners

Auto Failover

Circuit breaker handles node failures

Quick Start

import { TopGunClient, IDBAdapter } from '@topgunbuild/client';

const client = new TopGunClient({
  cluster: {
    seeds: [
      'ws://node1.example.com:8765',
      'ws://node2.example.com:8765',
      'ws://node3.example.com:8765',
    ],
    smartRouting: true,
  },
  storage: new IDBAdapter(),
});

await client.start();

// Use normally - routing is automatic
const users = client.getMap('users');
users.set('alice', { name: 'Alice', role: 'admin' });

Configuration

Seed Nodes

Seed nodes are the initial entry points to the cluster. The client connects to these nodes to discover the full cluster topology and receive the partition map.

cluster: {
  seeds: [
    'ws://node1:8765',  // Primary seed
    'ws://node2:8765',  // Backup seed
    'ws://node3:8765',  // Additional backup
  ],
}

Best Practice: Provide at least 2-3 seed nodes. If the first seed is unavailable, the client will try the next one. You don’t need to list all nodes—the client discovers them automatically.

Smart Routing

When enabled, the client maintains a partition map and routes each operation directly to the node that owns the relevant partition.

cluster: {
  seeds: ['ws://node1:8765', 'ws://node2:8765'],
  smartRouting: true,  // Default: true
}

How it works:

  1. Client receives partition map from the cluster (271 partitions)
  2. Each key is hashed to a partition ID using xxHash64
  3. Operation is sent directly to the partition owner
  4. If owner is unavailable, falls back to any available node

With Smart Routing

  • Lower latency (direct to owner)
  • Reduced cluster traffic
  • Better load distribution

Without Smart Routing

  • Simpler setup
  • Server handles forwarding
  • Good for small clusters

Connection Pool

Configure how many connections to maintain per node:

cluster: {
  seeds: ['ws://node1:8765'],
  connectionsPerNode: 2,      // Connections per node (default: 1)
  connectionTimeoutMs: 5000,  // Connection timeout (default: 10000)
}

Partition Map Refresh

The client periodically refreshes its partition map to stay in sync with cluster topology changes:

cluster: {
  seeds: ['ws://node1:8765'],
  partitionMapRefreshMs: 30000,  // Refresh every 30 seconds (default)
}

The partition map is also updated immediately when:

  • A node reports NOT_OWNER for an operation
  • A cluster topology change event is received

Failover Handling

The client uses a circuit breaker pattern to handle node failures gracefully.

Circuit Breaker States

StateBehavior
ClosedNormal operation, requests flow through
OpenNode marked failed, requests reroute to other nodes
Half-OpenTesting if node recovered, limited traffic allowed

Configuration

cluster: {
  seeds: ['ws://node1:8765'],
  // Circuit breaker is built-in with sensible defaults:
  // - Opens after 5 consecutive failures
  // - Resets after 30 seconds
  // - Transitions to half-open to test recovery
}

Behavior During Failover

// Your code doesn't need to handle failover explicitly
const users = client.getMap('users');

// This works even if the primary node is down
// The client automatically routes to available nodes
await users.set('key', { data: 'value' });

// Reads also work - served from any replica
const user = users.get('key');

Offline Support

If all nodes become unavailable, operations are queued locally in IndexedDB. When connectivity is restored, the client automatically syncs pending operations. This provides true offline-first capability even in cluster mode.

Monitoring

Connection State

// Listen for connection state changes
client.onConnectionChange((state) => {
  switch (state) {
    case 'connected':
      console.log('Connected to cluster');
      break;
    case 'connecting':
      console.log('Connecting to cluster...');
      break;
    case 'reconnecting':
      console.log('Reconnecting after failure...');
      break;
    case 'disconnected':
      console.log('Disconnected from cluster');
      break;
  }
});

Sync State

// Monitor synchronization progress
client.onSyncStateChange((state) => {
  if (state === 'syncing') {
    console.log('Syncing pending operations...');
  } else if (state === 'synced') {
    console.log('All operations synced');
  }
});

Best Practices

1

Use Multiple Seeds

Always provide 2-3 seed nodes for redundancy. If one is down during startup, the client can still connect via others.

2

Enable Smart Routing

Keep smartRouting: true for best performance. Only disable if you have specific requirements.

3

Handle Offline Gracefully

Your app should work offline. Use onSyncStateChange to show sync status to users.

4

Use Secure Connections

In production, use wss:// URLs for encrypted WebSocket connections.

Example: React Integration

// src/lib/topgun.ts
import { TopGunClient, IDBAdapter } from '@topgunbuild/client';

export const client = new TopGunClient({
  cluster: {
    seeds: [
      process.env.NEXT_PUBLIC_NODE1_URL!,
      process.env.NEXT_PUBLIC_NODE2_URL!,
      process.env.NEXT_PUBLIC_NODE3_URL!,
    ],
    smartRouting: true,
  },
  storage: new IDBAdapter(),
});

// Initialize on app startup
client.start();
// src/app/providers.tsx
import { TopGunProvider } from '@topgunbuild/react';
import { client } from '@/lib/topgun';

export function Providers({ children }) {
  return (
    <TopGunProvider client={client}>
      {children}
    </TopGunProvider>
  );
}