Unit 6.2 · Tutorial 2

Server-Sent Events & Scaling Realtime

Chapter 18 · Advanced Topics
~2.5 hours Advanced SSE · Redis Pub/Sub · Scaling

Overview

This tutorial covers Server-Sent Events (SSE) as an alternative to WebSockets for one‑way real‑time communication. You'll also learn how to scale real‑time applications using Redis Pub/Sub and horizontal scaling strategies for Socket.io servers.

Why this matters: Scaling real‑time applications is challenging. Redis Pub/Sub and horizontal scaling are essential for handling thousands of concurrent connections.

1. Server-Sent Events (SSE)

SSE allows a server to push events to clients over HTTP. It's simpler than WebSockets for one‑way communication.

Server (Node.js)

import express from 'express'; const app = express(); app.get('/events', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); let counter = 0; const interval = setInterval(() => { res.write(`data: ${JSON.stringify({ count: counter++, time: Date.now() })}\n\n`); }, 1000); req.on('close', () => { clearInterval(interval); res.end(); }); });

Client (browser)

const eventSource = new EventSource('/events'); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); console.log('Received:', data); }; eventSource.onerror = (error) => { console.error('SSE error:', error); }; eventSource.addEventListener('customEvent', (event) => { console.log('Custom event:', event.data); });

2. SSE vs WebSockets

Feature SSE WebSocket
Communication Server → Client only Bidirectional
Protocol HTTP WS/WSS
Reconnection Automatic Manual (Socket.io handles)
Binary data Limited (Base64) Full support
Use cases Live scores, news feeds, notifications Chat, gaming, collaborative tools

SSE is simpler and works over HTTP, making it easier to implement and scale.

3. Redis Pub/Sub for Scaling

Redis Pub/Sub allows Socket.io servers to share messages across multiple instances, enabling horizontal scaling.

# Install Redis adapter npm install @socket.io/redis-adapter npm install ioredis
// Socket.io with Redis adapter import { Server } from 'socket.io'; import { createClient } from 'redis'; import { createAdapter } from '@socket.io/redis-adapter'; const pubClient = createClient({ url: 'redis://localhost:6379' }); const subClient = pubClient.duplicate(); await Promise.all([pubClient.connect(), subClient.connect()]); const io = new Server(server); io.adapter(createAdapter(pubClient, subClient)); // Now all Socket.io servers share the same Redis connection io.on('connection', (socket) => { // Any 'message' emitted from any server instance // will be delivered to clients on all instances socket.on('message', (data) => { io.emit('message', data); // Broadcasts across all instances }); });
// Using Redis directly for Pub/Sub const pub = createClient(); const sub = pub.duplicate(); // Publisher await pub.connect(); await pub.publish('notifications', JSON.stringify({ message: 'Hello' })); // Subscriber await sub.connect(); sub.subscribe('notifications', (message) => { const data = JSON.parse(message); // Process notification io.emit('notification', data); });

4. Horizontal Scaling Strategies

Sticky sessions

Some applications require sticky sessions to maintain state. Use a load balancer that supports session affinity.

Redis for state sharing

Use Redis to store session data, room memberships, and other state that needs to be shared across instances.

Database-backed state

For persistent state (e.g., message history), use a database like PostgreSQL or MongoDB instead of in‑memory storage.

// Scaling configuration (Nginx example) upstream socket_servers { least_conn; server 10.0.0.1:3000; server 10.0.0.2:3000; server 10.0.0.3:3000; } server { listen 80; location /socket.io/ { proxy_pass http://socket_servers; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
Scaling checklist:
  1. Use Redis Pub/Sub for inter‑instance communication
  2. Store room data in Redis (not memory)
  3. Use a load balancer with WebSocket support
  4. Monitor connection count and scale horizontally

Quiz

Question 1

What is the primary advantage of SSE over WebSockets?

  • Bidirectional communication
  • Simplicity and built‑in reconnection
  • Binary data support
  • Lower latency
Show answer
B. Simplicity and built‑in reconnection.

Question 2

Which Redis feature enables Socket.io scaling?

  • Redis Cache
  • Redis Pub/Sub
  • Redis Streams
  • Redis Lists
Show answer
B. Redis Pub/Sub.

Question 3

What is the MIME type for Server-Sent Events?

  • text/plain
  • text/event-stream
  • application/json
  • application/octet-stream
Show answer
B. text/event-stream.

Exercises

Exercise 1

Implement an SSE endpoint that streams random stock prices for 3 different stocks every 500ms.

Sample answer
app.get('/stocks', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const stocks = ['AAPL', 'GOOGL', 'MSFT']; const interval = setInterval(() => { const data = stocks.map(symbol => ({ symbol, price: 100 + Math.random() * 200 })); res.write(`data: ${JSON.stringify(data)}\n\n`); }, 500); req.on('close', () => { clearInterval(interval); res.end(); }); });

Exercise 2

Configure Socket.io with Redis adapter for horizontal scaling.

Sample answer
import { Server } from 'socket.io'; import { createClient } from 'redis'; import { createAdapter } from '@socket.io/redis-adapter'; const pubClient = createClient({ url: 'redis://redis:6379' }); const subClient = pubClient.duplicate(); await pubClient.connect(); await subClient.connect(); const io = new Server(server, { adapter: createAdapter(pubClient, subClient) });

Homework

Homework 1

Scale the chat application from Tutorial 1 using Redis Pub/Sub. Deploy two instances of the server and verify that messages are synchronised between them.

Sample outline
  • Setup: Install Redis and Socket.io Redis adapter.
  • Configure: Use the Redis adapter for Socket.io.
  • Run: Start two server instances on different ports.
  • Test: Connect from two clients to different servers and verify messages are broadcast to both.

Mini‑Project

Scalable Notification Service

Build a scalable notification service that:

  • Uses SSE for one‑way real‑time notifications
  • Uses Redis Pub/Sub for distributing notifications across multiple server instances
  • Stores notification history in a database (PostgreSQL)
  • Can scale horizontally with multiple instances
Sample outline
  • SSE endpoint: /api/notifications/stream
  • Redis: Pub/Sub for inter‑instance communication
  • Database: PostgreSQL with Prisma for notification history
  • Publisher: Any instance can publish notifications
  • Consumer: Each instance subscribes and pushes to connected clients

Tutorial Summary

You learned Server-Sent Events (SSE) as a simpler alternative to WebSockets for one‑way real‑time communication. You also mastered scaling real‑time applications using Redis Pub/Sub and horizontal scaling strategies. These skills are essential for building production‑grade real‑time systems.

Key takeaway: Choose the right tool for the job: SSE for one‑way streaming, WebSockets for bidirectional. Always plan for scaling from the start using Redis and stateless design.