Real-Time Sync
Enable instant updates across all connected clients with WebSocket and Redis broadcasting.
How It Works
Client A Server Pod 1 Redis Server Pod 2 Client B
│ │ │ │ │
│ 1. Mutation │ │ │ │
├────POST /tx────────────>│ │ │ │
│ │ │ │ │
│ │ 2. Write SyncAction │ │ │
│ ├─────to Database──────┤ │ │
│ │ │ │ │
│ │ 3. Publish Frame │ │ │
│ ├──────PUBLISH─────────>│ │ │
│ │ │ 4. Broadcast │ │
│ │ ├──────SUBSCRIBE───────>│ │
│ │ │ │ 5. Push to WebSocket │
│ │ │ ├──────WS Frame─────────>│
│ 6. Response │ │ │ │
│<────{ ok: true }────────┤ │ │ │
│ │ │ │ │Single Server (Default)
WebSocket works automatically without additional configuration:
import { SyncServer } from '@gluonic/server'
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET })
// No broadcaster needed - WebSocket works for this pod!
})Behavior:
- ✅ WebSocket connections to this pod receive updates
- ❌ WebSocket connections to other pods don’t receive updates
- Use case: Development, single-server production
With Broadcaster (Production)
Add Redis broadcaster for multi-pod deployments and cross-service updates:
import { SyncServer } from '@gluonic/server'
import { RedisBroadcaster } from '@gluonic/broadcaster-redis'
const server = SyncServer({
database,
auth: JWTAuth({ secret: process.env.JWT_SECRET }),
broadcaster: RedisBroadcaster({ url: 'redis://localhost:6379' })
})API Reference: RedisBroadcaster - Broadcasting configuration
Behavior:
- ✅ Changes broadcast to ALL pods via Redis pub/sub
- ✅ ALL connected clients receive updates (any pod)
- ✅ Horizontal scaling works!
Configuration
Redis URL Formats
// Local development
'redis://localhost:6379'
// With password
'redis://:password@localhost:6379'
// AWS ElastiCache
'redis://my-cluster.abc123.use1.cache.amazonaws.com:6379'
// With TLS
'rediss://:password@my-cluster.abc123.use1.cache.amazonaws.com:6379'Auto-Detection from Environment
const redisUrl =
process.env.REDIS_URL ||
(process.env.REDIS_ENDPOINT && process.env.REDIS_PORT
? `redis://${process.env.REDIS_ENDPOINT}:${process.env.REDIS_PORT}`
: null)
if (redisUrl) {
broadcaster = makeRedisBroadcaster(redisUrl)
}Client Connection
WebSocket Setup (Automatic)
Clients connect via createSyncClient():
const { store } = createSyncClient({
server: {
http: 'https://api.example.com/sync/v1',
websocket: 'wss://api.example.com/sync/v1' // Auto-appends /ws
},
// ... other config
})Connection Lifecycle
// 1. Client calls store.catchUp()
await store.catchUp()
// 2. WebSocket connects
WebSocket → wss://api.example.com/sync/v1/ws?token=JWT_TOKEN
// 3. Server authenticates
auth(req) // Verifies token
// 4. Connection established
// Client added to org's socket pool
// 5. Frames pushed automatically
// Server → Client via WebSocket
// 6. Client applies frames
store.applyFrames(frames)
// UI updates in real-time ✓Broadcast Flow
When Mutation Happens
// 1. Client sends mutation
POST /sync/v1/tx {
ops: [{ t: 'post', id: '123', op: 'u', patch: { title: 'New' } }]
}
// 2. Server writes SyncAction
await adapter.writeSyncAction(orgId, {
model: 'post',
modelId: '123',
op: 'u',
patch: { title: 'New' },
actorId: userId
})
// 3. Server publishes to Redis
await broadcaster.publish(orgId, [{
sid: 15,
t: 'post',
id: '123',
op: 'u',
p: { title: 'New' },
who: userId,
at: Date.now()
}])
// 4. ALL pods receive via Redis subscription
// Each pod pushes to its local WebSocket clients
// 5. Clients apply frame
// UI updates across all devices ✓Multi-Tab Sync
With Redis, multi-tab sync works automatically:
// Browser Tab 1
await store.save('post', '123', { title: 'Tab 1 Edit' })
// Broadcast flow:
// Tab 1 → Server → Redis → Server → Tab 2
// Browser Tab 2 (same browser!)
// Receives frame via WebSocket
// UI updates automatically ✓Heartbeat & Reconnection
Server Heartbeat
// Server sends heartbeat every 10 seconds
setInterval(() => {
for (const ws of sockets) {
ws.ping() // WebSocket protocol ping
ws.send(JSON.stringify({ type: 'heartbeat' })) // App-level heartbeat
}
}, 10000)Client Reconnection
// Client handles disconnections automatically
ws.onclose = () => {
console.log('WebSocket disconnected, reconnecting...')
// Exponential backoff
setTimeout(() => {
reconnect()
}, Math.min(1000 * Math.pow(2, attempt), 30000))
}
// After reconnecting:
// - Delta sync catches up on missed changes
// - WebSocket resumes real-time updatesPerformance
Frame Batching
Large batches are chunked to avoid WebSocket limits:
// Server config
const MAX_FRAMES_PER_MESSAGE = 500
// If 2000 frames to broadcast:
// Chunk 1: frames 0-499
// Chunk 2: frames 500-999
// Chunk 3: frames 1000-1499
// Chunk 4: frames 1500-1999
// Sent as separate WebSocket messages
// Client processes chunks sequentiallyBackpressure
Client-side backpressure prevents overload:
// Client queues incoming frames
const frameQueue = []
ws.onmessage = (evt) => {
const frames = JSON.parse(evt.data)
frameQueue.push(...frames)
}
// Process queue at controlled rate
setInterval(() => {
const batch = frameQueue.splice(0, 100)
if (batch.length > 0) {
store.applyFrames(batch)
}
}, 100)Monitoring
Connection Count
// Get current WebSocket connections
const metrics = await fetch('http://localhost:3000/sync/v1/metrics')
.then(r => r.json())
console.log('Active connections:', metrics.wsConnections)Broadcast Metrics
{
"wsConnections": 42, // Active WebSocket clients
"wsBroadcastedFrames": 1500, // Total frames sent
"publishLatencyMsTotal": 2500, // Total latency
"publishLatencyCount": 1500 // Avg = 1.67ms per frame
}Troubleshooting
WebSocket connections immediately close
Problem: Connections open then close within 1 second.
Solutions:
- Check auth function handles WebSocket token (query param)
- Verify Redis is running (if using broadcaster)
- Check firewall allows WebSocket (ws/wss protocol)
- Check CORS allows WebSocket upgrade
No real-time updates
Problem: Mutations succeed but other clients don’t update.
Solutions:
- Verify Redis broadcaster is configured
- Check Redis connection is working
- Ensure all pods subscribe to Redis
- Check WebSocket connections are established
Redis connection errors
Problem: Error: Redis connection failed
Solutions:
- Verify Redis URL is correct
- Check Redis server is running
- Test connection:
redis-cli ping - Check network/firewall rules
Next Steps
- Snapshots - Cache for faster bootstrap
- Performance - Scale to production
- Deployment - Deploy with Redis
Last updated on