Skip to main content
backend26 ottobre 202515 min di lettura

Dati in Tempo Reale: WebSockets, SSE e Quando Ne Hai Veramente Bisogno

Comprendere i modelli di dati in tempo reale — da WebSockets a Server-Sent Events, Firebase Realtime Database, e sapere quando il polling è sufficiente.

websocketsrealtimesse
Dati in Tempo Reale: WebSockets, SSE e Quando Ne Hai Veramente Bisogno

La maggior parte delle applicazioni che affermano di aver bisogno di dati in tempo reale in realtà non ne hanno bisogno. Hanno bisogno di dati che sembrino attuali — aggiornati entro pochi secondi, non pochi millisecondi. La distinzione è importante perché le vere architetture in tempo reale introducono complessità nella gestione delle connessioni, nella sincronizzazione dello stato, nello scaling e nella gestione degli errori che sono del tutto inutili se i tuoi utenti fossero perfettamente soddisfatti di un ritardo di cinque secondi.

Ho costruito funzionalità veramente in tempo reale — tracciamento degli ordini in tempo reale per una piattaforma di ristorazione, interfacce di editing collaborativo, dashboard live — e ho anche costruito funzionalità in cui inizialmente ho sovra-ingegnerizzato con WebSockets e in seguito le ho sostituite con un polling che si aggiorna ogni dieci secondi. Nessuno ha notato il cambiamento. Iniziare con l'approccio più semplice e aggiungere complessità solo quando gli utenti riscontrano effettivamente un problema non è solo pragmatico — è la scelta ingegneristica responsabile.

Quando Hai Veramente Bisogno del Tempo Reale

Il vero tempo reale è giustificato quando il valore delle informazioni si degrada rapidamente con la latenza.

Chat e messaggistica. Gli utenti si aspettano che i messaggi appaiano entro un secondo. Un ritardo di cinque secondi fa sentire una conversazione interrotta. Il tempo reale non è opzionale qui.

Editing collaborativo. L'editing simultaneo in stile Google Docs richiede una sincronizzazione inferiore al secondo per evitare modifiche in conflitto. La complessità qui va oltre il semplice trasporto — sono necessarie trasformazioni operative o CRDT per la risoluzione dei conflitti.

Dashboard live con impatto operativo. Una dashboard di monitoraggio in cui un ingegnere osserva le anomalie durante un deployment necessita di aggiornamenti in tempo reale. Una dashboard di analisi aziendale visualizzata una volta al giorno no.

Gaming ed esperienze interattive. Giochi multiplayer, aste dal vivo, sondaggi in tempo reale — qualsiasi cosa in cui più utenti interagiscono simultaneamente con uno stato condiviso.

Trading finanziario. Feed di prezzi, aggiornamenti del book degli ordini, cambiamenti di posizione. I millisecondi contano qui, e l'architettura lo riflette.

Quando Non Hai Bisogno del Tempo Reale

Feed dei social media. Twitter e Instagram sembrano in tempo reale, ma utilizzano una combinazione di polling al focus e notifiche push. Il feed non si aggiorna mentre lo stai guardando — devi tirare per aggiornare.

Inventario e-commerce. "Solo 3 rimasti!" non ha bisogno di aggiornarsi in tempo reale. Controllare al caricamento della pagina e al checkout è sufficiente.

Conteggi delle notifiche. Il badge rosso che mostra "5 nuove notifiche" può essere interrogato ogni 30 secondi. Gli utenti non notano se una notifica appare 30 secondi dopo essere stata creata.

Aggiornamenti dei contenuti. Post di blog, elenchi di prodotti, profili utente — qualsiasi dato che cambia infrequentemente. Effettua il polling al caricamento della pagina o usa le intestazioni della cache HTTP con la riconvalida.

Polling: L'Opzione Predefinita Sottovalutata

Il polling — effettuare richieste HTTP periodiche per verificare la presenza di nuovi dati — è l'approccio più semplice e funziona per più casi d'uso di quanto la maggior parte degli sviluppatori non presuma.

Polling Semplice

function usePolledData<T>(url: string, intervalMs: number = 5000) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let active = true;

    const fetchData = async () => {
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const result = await response.json();
        if (active) setData(result);
      } catch (e) {
        if (active) setError(e as Error);
      }
    };

    fetchData(); // Initial fetch
    const interval = setInterval(fetchData, intervalMs);

    return () => {
      active = false;
      clearInterval(interval);
    };
  }, [url, intervalMs]);

  return { data, error };
}

Polling Intelligente con Richieste Condizionali

Le richieste condizionali HTTP (If-None-Match con ETags o If-Modified-Since) consentono di effettuare il polling in modo efficiente. Il server restituisce 304 Not Modified senza corpo se i dati non sono cambiati, riducendo la larghezza di banda a quasi zero per le risorse invariate.

async function pollWithETag(url: string): Promise<{ data: any; changed: boolean }> {
  const cachedETag = etagCache.get(url);

  const headers: HeadersInit = {};
  if (cachedETag) {
    headers['If-None-Match'] = cachedETag;
  }

  const response = await fetch(url, { headers });

  if (response.status === 304) {
    return { data: dataCache.get(url), changed: false };
  }

  const etag = response.headers.get('ETag');
  if (etag) etagCache.set(url, etag);

  const data = await response.json();
  dataCache.set(url, data);

  return { data, changed: true };
}

Quando il Polling Fallisce

Il polling smette di essere praticabile quando:

  • Hai bisogno di una latenza inferiore al secondo. Effettuare il polling ogni 500ms è essenzialmente un attacco DoS alla tua stessa API.
  • I dati cambiano raramente ma devono essere consegnati istantaneamente. Effettuare il polling ogni 5 secondi per un evento che accade una volta all'ora spreca 719 richieste all'ora.
  • Hai migliaia di client. Ogni client che effettua il polling è una connessione indipendente. Su larga scala, l'overhead HTTP di stabilire connessioni, analizzare le intestazioni e autenticare si somma.

Server-Sent Events: Il Tempo Reale Più Semplice

I Server-Sent Events (SSE) sono la tecnologia in tempo reale più sottoutilizzata nello sviluppo web. Gli SSE forniscono un flusso unidirezionale dal server al client tramite una connessione HTTP standard. Il browser gestisce automaticamente la riconnessione e il protocollo è trivialmente semplice.

Implementazione del Server

// Express.js SSE endpoint
app.get('/events/orders/:restaurantId', (req, res) => {
  const { restaurantId } = req.params;

  // SSE headers
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'X-Accel-Buffering': 'no', // Disable nginx buffering
  });

  // Send initial data
  const initialOrders = getActiveOrders(restaurantId);
  res.write(`data: ${JSON.stringify(initialOrders)}\n\n`);

  // Subscribe to order changes
  const unsubscribe = orderEmitter.on(`orders:${restaurantId}`, (order) => {
    res.write(`event: orderUpdate\n`);
    res.write(`data: ${JSON.stringify(order)}\n`);
    res.write(`id: ${order.id}-${order.updatedAt}\n\n`);
  });

  // Heartbeat to detect dead connections
  const heartbeat = setInterval(() => {
    res.write(`: heartbeat\n\n`);
  }, 30000);

  // Cleanup on disconnect
  req.on('close', () => {
    clearInterval(heartbeat);
    unsubscribe();
  });
});

Implementazione del Client

function useSSE(url: string) {
  const [data, setData] = useState(null);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const eventSource = new EventSource(url);

    eventSource.onopen = () => setConnected(true);

    eventSource.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };

    eventSource.addEventListener('orderUpdate', (event) => {
      const order = JSON.parse(event.data);
      setData(prev => updateOrderInList(prev, order));
    });

    eventSource.onerror = () => {
      setConnected(false);
      // EventSource automatically reconnects
    };

    return () => eventSource.close();
  }, [url]);

  return { data, connected };
}

Perché SSE Invece di WebSockets

Riconnessione automatica. L'implementazione di EventSource del browser gestisce automaticamente la riconnessione con backoff esponenziale. Con i WebSockets, devi implementarlo tu stesso.

Funziona tramite proxy e bilanciatori di carico. SSE utilizza HTTP standard, quindi funziona tramite nginx, CloudFlare e la maggior parte dei reverse proxy senza configurazioni speciali. I WebSockets richiedono un supporto esplicito del proxy.

Autenticazione più semplice. Le connessioni SSE trasportano i cookie e possono utilizzare le intestazioni di autenticazione HTTP standard. L'autenticazione WebSocket richiede l'invio delle credenziali dopo che la connessione è stata stabilita, il che è un protocollo aggiuntivo da implementare.

Tipi di eventi e ID integrati. Il protocollo SSE supporta eventi nominati e ID di messaggi, che consentono al client di riprendere da dove aveva interrotto dopo una riconnessione.

Limitazioni degli SSE

  • Solo unidirezionale. Dal server al client. Se hai bisogno di comunicazione bidirezionale, SSE non può farlo. Puoi abbinare SSE a normali richieste HTTP POST per i messaggi client-server, il che funziona bene per molti casi d'uso.
  • Limiti di connessione del browser. I browser limitano il numero di connessioni HTTP simultanee a un singolo dominio (tipicamente sei). Ogni connessione SSE conta per questo limite. Il multiplexing HTTP/2 mitiga questo problema, ma è bene esserne consapevoli.
  • Nessun dato binario. SSE è solo testo. Se hai bisogno di trasmettere dati binari (audio, video, file), usa WebSockets o un meccanismo separato.

WebSockets: Comunicazione Full Duplex

I WebSockets forniscono comunicazione full-duplex — sia il client che il server possono inviare messaggi in qualsiasi momento senza l'overhead dei cicli di richiesta/risposta HTTP. Questo è necessario quando hai bisogno di comunicazione bidirezionale a bassa latenza.

Implementazione del Server con ws

import { WebSocketServer, WebSocket } from 'ws';
import { createServer } from 'http';

const server = createServer();
const wss = new WebSocketServer({ server });

// Connection management
const rooms = new Map<string, Set<WebSocket>>();

wss.on('connection', (ws, req) => {
  // Authenticate the connection
  const token = new URL(req.url!, `http://${req.headers.host}`).searchParams.get('token');
  const user = verifyToken(token);

  if (!user) {
    ws.close(4001, 'Unauthorized');
    return;
  }

  // Attach user data
  (ws as any).userId = user.id;

  ws.on('message', (raw) => {
    try {
      const message = JSON.parse(raw.toString());
      handleMessage(ws, user, message);
    } catch (e) {
      ws.send(JSON.stringify({ error: 'Invalid message format' }));
    }
  });

  ws.on('close', () => {
    // Remove from all rooms
    rooms.forEach((clients, roomId) => {
      clients.delete(ws);
      if (clients.size === 0) rooms.delete(roomId);
    });
  });

  // Heartbeat
  (ws as any).isAlive = true;
  ws.on('pong', () => { (ws as any).isAlive = true; });
});

// Dead connection detection
const heartbeatInterval = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!(ws as any).isAlive) {
      ws.terminate();
      return;
    }
    (ws as any).isAlive = false;
    ws.ping();
  });
}, 30000);

function handleMessage(ws: WebSocket, user: any, message: any) {
  switch (message.type) {
    case 'join_room':
      joinRoom(ws, message.roomId);
      break;
    case 'leave_room':
      leaveRoom(ws, message.roomId);
      break;
    case 'broadcast':
      broadcastToRoom(message.roomId, {
        type: 'message',
        userId: user.id,
        content: message.content,
        timestamp: Date.now(),
      }, ws);
      break;
  }
}

function joinRoom(ws: WebSocket, roomId: string) {
  if (!rooms.has(roomId)) rooms.set(roomId, new Set());
  rooms.get(roomId)!.add(ws);
}

function broadcastToRoom(roomId: string, data: any, exclude?: WebSocket) {
  const clients = rooms.get(roomId);
  if (!clients) return;

  const payload = JSON.stringify(data);
  clients.forEach((client) => {
    if (client !== exclude && client.readyState === WebSocket.OPEN) {
      client.send(payload);
    }
  });
}

server.listen(8080);

Implementazione del Client con Riconnessione

class WebSocketClient {
  private ws: WebSocket | null = null;
  private url: string;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 10;
  private handlers = new Map<string, Set<(data: any) => void>>();

  constructor(url: string) {
    this.url = url;
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      this.reconnectAttempts = 0;
      this.emit('connected', null);
    };

    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        this.emit(message.type, message);
      } catch (e) {
        console.error('Failed to parse WebSocket message:', e);
      }
    };

    this.ws.onclose = (event) => {
      this.emit('disconnected', { code: event.code, reason: event.reason });

      if (event.code !== 4001 && this.reconnectAttempts < this.maxReconnectAttempts) {
        this.scheduleReconnect();
      }
    };

    this.ws.onerror = () => {
      // Error is followed by close event, handle reconnection there
    };
  }

  private scheduleReconnect() {
    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
    this.reconnectAttempts++;

    setTimeout(() => {
      this.emit('reconnecting', { attempt: this.reconnectAttempts });
      this.connect();
    }, delay);
  }

  send(type: string, data: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type, ...data }));
    }
  }

  on(event: string, handler: (data: any) => void) {
    if (!this.handlers.has(event)) this.handlers.set(event, new Set());
    this.handlers.get(event)!.add(handler);
    return () => this.handlers.get(event)?.delete(handler);
  }

  private emit(event: string, data: any) {
    this.handlers.get(event)?.forEach((handler) => handler(data));
  }

  disconnect() {
    this.maxReconnectAttempts = 0; // Prevent reconnection
    this.ws?.close();
  }
}

Strategia di Riconnessione

La logica di riconnessione merita attenzione perché influisce direttamente sull'esperienza utente e sul carico del server.

Backoff esponenziale previene i problemi di "thundering herd". Se il tuo server si riavvia e 10.000 client si riconnettono simultaneamente, il server si blocca di nuovo. Aggiungendo jitter e ritardi esponenziali, le connessioni si distribuiscono nel tempo:

function getReconnectDelay(attempt: number): number {
  const baseDelay = 1000;
  const maxDelay = 30000;
  const exponentialDelay = baseDelay * Math.pow(2, attempt);
  const jitter = Math.random() * 1000; // Random jitter up to 1 second
  return Math.min(exponentialDelay + jitter, maxDelay);
}

L'interfaccia utente dello stato della connessione è importante. Gli utenti devono sapere quando sono disconnessi e quando l'app sta tentando di riconnettersi. Un piccolo banner che dice "Riconnessione in corso..." è meglio che mostrare silenziosamente dati obsoleti.

Socket.IO: Convenienza vs Controllo

Socket.IO è la libreria WebSocket più popolare e offre una notevole comodità: riconnessione automatica, gestione delle stanze, riconoscimenti e fallback al polling quando i WebSockets non sono disponibili. Aggiunge anche un overhead significativo e introduce un livello di astrazione che può mascherare i problemi.

Quando Usare Socket.IO

  • Prototipazione rapida. L'API di Socket.IO è più semplice dei WebSockets grezzi e le funzionalità integrate fanno risparmiare tempo di sviluppo.
  • Quando hai bisogno di un fallback al polling. Alcune reti aziendali bloccano le connessioni WebSocket. Socket.IO ricade automaticamente sul long polling HTTP.
  • Gestione di stanze e namespace. Se la tua funzionalità in tempo reale si mappa naturalmente a stanze (chat room, lobby di gioco, documenti collaborativi), le astrazioni delle stanze di Socket.IO fanno risparmiare codice boilerplate.

Quando Evitare Socket.IO

  • Applicazioni ad alte prestazioni. Il protocollo di Socket.IO aggiunge overhead a ogni messaggio (framing, codifica, metadati). Per applicazioni ad alto throughput, i WebSockets grezzi sono misurabilmente più veloci.
  • Quando hai bisogno di interoperabilità. Socket.IO non è uno standard WebSocket — un normale client WebSocket non può connettersi a un server Socket.IO. Se i tuoi client includono ambienti non JavaScript (app mobili, dispositivi IoT), Socket.IO richiede una libreria client per ogni piattaforma.
  • Quando vuoi capire cosa sta succedendo. Socket.IO astrae la gestione delle connessioni, il che è comodo finché qualcosa non va storto. Il debug di un problema di connessione Socket.IO spesso significa comprendere il livello di astrazione sopra il problema reale.

Firebase Realtime Database e Firestore

Firebase offre funzionalità in tempo reale senza gestire alcuna infrastruttura WebSocket. Questa è la sua principale proposta di valore — scambi il controllo con la comodità.

Firebase Realtime Database

import { getDatabase, ref, onValue, set } from 'firebase/database';

const db = getDatabase();

// Listen for real-time updates
const ordersRef = ref(db, `restaurants/${restaurantId}/orders`);
onValue(ordersRef, (snapshot) => {
  const orders = snapshot.val();
  updateUI(orders);
});

// Write data (triggers listeners on all connected clients)
await set(ref(db, `restaurants/${restaurantId}/orders/${orderId}`), {
  status: 'preparing',
  updatedAt: Date.now(),
});

Il Realtime Database è un albero JSON. Ogni scrittura a qualsiasi nodo si propaga immediatamente a ogni client in ascolto su quel nodo o su qualsiasi nodo genitore. Questo è potente ma pericoloso — un listener su un nodo di alto livello riceve aggiornamenti per ogni cambiamento nell'intero sottoalbero.

Listener in Tempo Reale di Firestore

import { getFirestore, collection, onSnapshot, query, where } from 'firebase/firestore';

const db = getFirestore();

// Listen for active orders
const q = query(
  collection(db, 'orders'),
  where('restaurantId', '==', restaurantId),
  where('status', 'in', ['pending', 'preparing', 'ready'])
);

const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === 'added') addOrder(change.doc.data());
    if (change.type === 'modified') updateOrder(change.doc.data());
    if (change.type === 'removed') removeOrder(change.doc.id);
  });
});

I listener in tempo reale di Firestore sono più granulari del Realtime Database — puoi ascoltare query specifiche, non solo percorsi. Il metodo docChanges() ti dice esattamente cosa è cambiato, il che rende possibili aggiornamenti efficienti dell'interfaccia utente.

Supabase Realtime

Supabase offre funzionalità in tempo reale basate su PostgreSQL, che ti offre la potenza di query di SQL con aggiornamenti in tempo reale. Utilizza le funzionalità di replica di PostgreSQL (replica logica e il WAL) per rilevare i cambiamenti e trasmetterli tramite canali WebSocket.

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(url, key);

// Listen for changes to the orders table
const channel = supabase
  .channel('orders')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'orders',
      filter: `restaurant_id=eq.${restaurantId}`,
    },
    (payload) => {
      console.log('Change type:', payload.eventType);
      console.log('New data:', payload.new);
      console.log('Old data:', payload.old);
    }
  )
  .subscribe();

Il vantaggio rispetto a Firebase è chiaro: i tuoi dati risiedono in PostgreSQL con tutte le capacità di query SQL, transazioni ACID e strumenti standard. Il livello in tempo reale è un'aggiunta, non un sostituto per il tuo motore di query. Il compromesso è che Supabase Realtime è meno collaudato su larga scala rispetto a Firebase, e le prestazioni in tempo reale dipendono dal throughput di replica di PostgreSQL.

Scalare le Connessioni WebSocket

Un singolo server può gestire decine di migliaia di connessioni WebSocket, ma scalare oltre un server introduce un problema di coordinamento: un messaggio pubblicato sul Server A deve raggiungere i client connessi al Server B.

Il Pattern Pub/Sub

Redis pub/sub è la soluzione standard:

import { createClient } from 'redis';

const publisher = createClient();
const subscriber = createClient();

await publisher.connect();
await subscriber.connect();

// When a message comes in on any server, publish to Redis
function publishToRoom(roomId: string, message: any) {
  publisher.publish(`room:${roomId}`, JSON.stringify(message));
}

// Each server subscribes and forwards to its local connections
await subscriber.subscribe(`room:${roomId}`, (message) => {
  const data = JSON.parse(message);
  broadcastToLocalClients(roomId, data);
});

Questo pattern ti permette di eseguire più server WebSocket dietro un bilanciatore di carico. Ogni server gestisce le proprie connessioni e Redis coordina la comunicazione tra i server.

Sessioni Sticky

Le connessioni WebSocket sono stateful — una volta stabilite, devono rimanere sullo stesso server. I bilanciatori di carico necessitano di sessioni sticky (chiamate anche affinità di sessione) per instradare la connessione WebSocket di un client allo stesso server che ha gestito l'upgrade HTTP iniziale.

Con nginx:

upstream websocket_servers {
    ip_hash;  # Sticky sessions based on client IP
    server ws1.example.com:8080;
    server ws2.example.com:8080;
}

server {
    location /ws {
        proxy_pass http://websocket_servers;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;  # 24 hours
    }
}

Tempo Reale vs Quasi Tempo Reale

Esiste uno spettro tra "controlla gli aggiornamenti quando l'utente ricarica la pagina" e "consegna ogni cambiamento entro millisecondi". La maggior parte delle funzionalità rientra da qualche parte nel mezzo.

Approccio Latenza Complessità Ideale Per
Aggiornamento pagina Iniziato dall'utente Nessuna Contenuti statici, cambiamenti infrequenti
Polling (30s) Fino a 30 secondi Bassa Conteggi notifiche, aggiornamenti dashboard
Polling (5s) Fino a 5 secondi Bassa Feed attività, stato ordini
Long polling Inferiore al secondo Moderata Sistemi più vecchi, ambienti ostili ai proxy
SSE Inferiore al secondo Moderata Feed live, notifiche, stream unidirezionali
WebSockets Inferiore al secondo Alta Chat, collaborazione, tempo reale bidirezionale
WebRTC Millisecondi Molto alta Video/audio, peer-to-peer

L'approccio giusto è quello più semplice che soddisfa il tuo requisito di latenza. Inizia con il polling. Se gli utenti si lamentano dell'obsolescenza, passa agli SSE. Se hai bisogno di comunicazione bidirezionale, usa i WebSockets. Ogni passo verso l'alto nella scala della complessità dovrebbe essere giustificato da una concreta esigenza dell'utente, non da un'ipotetica esigenza futura.

Raccomandazioni Pratiche

Dopo aver costruito funzionalità in tempo reale in diversi progetti, questi sono i pattern a cui torno sempre:

Predefinisci SSE per il tempo reale unidirezionale. La maggior parte delle funzionalità in tempo reale sono server-to-client: aggiornamenti degli ordini, stream di notifiche, feed di dati live. SSE gestisce tutto questo con meno complessità rispetto ai WebSockets.

Usa i WebSockets solo per esigenze bidirezionali. Chat, editing collaborativo, interazioni multiplayer — questi necessitano veramente dei WebSockets. Se il tuo client riceve solo dati, SSE è più semplice.

Implementa sempre la riconnessione con backoff. Le connessioni cadono. Le reti cambiano. I server si riavviano. Il tuo client deve gestire questo con grazia, e il tuo server deve sopravvivere a una tempesta di riconnessioni.

Invia differenze, non lo stato completo. Invece di inviare l'intero elenco degli ordini ogni volta che un ordine cambia, invia solo l'ordine modificato. Il client applica la differenza al suo stato locale. Questo riduce la larghezza di banda e l'elaborazione su entrambi i lati.

Prevedi un fallback. Se la tua connessione WebSocket fallisce e non riesce a riconnettersi, l'applicazione dovrebbe comunque essere funzionale. Un indicatore "ultimo aggiornamento 30 secondi fa" con aggiornamento manuale è meglio di una schermata vuota.

Monitora il numero di connessioni in produzione. Le connessioni WebSocket consumano risorse del server (memoria, descrittori di file). Un picco improvviso di connessioni — ad esempio, a causa di un bug del client che causa riconnessioni rapide — può far cadere il tuo server. Allerta sulle anomalie del conteggio delle connessioni.

L'obiettivo non è avere l'architettura in tempo reale più sofisticata. L'obiettivo è consegnare i dati agli utenti abbastanza velocemente da far sì che l'applicazione sembri reattiva, con la minima complessità infrastrutturale che raggiunga quella sensazione.

DU

Danil Ulmashev

Full Stack Developer

Interessato a collaborare?