Scaling orizzontale vs verticale: la scelta fondamentale
Ogni team di produzione arriva prima o poi al momento in cui un singolo server non è più sufficiente. La risposta istintiva è spesso acquistare una macchina più potente — più core, più RAM, SSD più veloci. Questo è lo scaling verticale (scale-up), ed è un primo passo del tutto legittimo. Un server più grande non richiede modifiche all'applicazione, nessuna complessità infrastrutturale e nessun ripensamento del livello dati. Per i prodotti nelle fasi iniziali, lo scaling verticale porta lontano: un moderno server a 32 core e 128 GB di RAM può comodamente gestire decine di migliaia di utenti simultanei su un'applicazione web monolitica ben ottimizzata.
Il problema è il limite massimo. Una volta scalato alla più grande istanza disponibile, non c'è più margine — e a quel punto si fa girare l'intero carico di produzione su un single point of failure. Un guasto hardware, un kernel panic o un'interruzione di zona di disponibilità mette tutto giù contemporaneamente. Ecco perché l'architettura cloud-native tratta lo scaling verticale come sollievo temporaneo, non come strategia.
Lo scaling orizzontale (scale-out) significa distribuire il carico su più istanze server identiche. Non ha limiti teorici: si aggiungono istanze man mano che il carico cresce e si rimuovono quando il traffico diminuisce. Questa elasticità è il fondamento della scalabilità moderna delle app web. Ma lo scaling orizzontale ha un prerequisito rigoroso: l'applicazione deve essere progettata per funzionare su molti server contemporaneamente, ciascuno ignaro dello stato detenuto dai suoi pari.
| Dimensione | Scaling verticale | Scaling orizzontale |
|---|---|---|
| Limite massimo | Fisso (più grande istanza disponibile) | Nessuno in pratica |
| Modifiche applicazione richieste | Nessuna | Statelessness, livello dati condiviso |
| Tolleranza ai guasti | Single point of failure | Ridondante — le istanze falliscono indipendentemente |
| Efficienza dei costi alla scala | Bassa — istanze grandi costose | Alta — istanze commodity, si paga solo la capacità usata |
| Complessità di deployment | Bassa | Moderata (load balancer, service discovery) |
| Tempo di implementazione | Minuti (ridimensionamento istanza) | Giorni o settimane (refactoring architetturale) |
In pratica, la maggior parte dei team usa una combinazione: lo scaling verticale compra tempo mentre lo scaling orizzontale viene sviluppato. Come discutiamo nella nostra guida sull'architettura monolite vs microservizi, un monolite ben strutturato scala orizzontalmente in modo altrettanto efficace di un service mesh — la chiave è la statelessness, non la topologia di deployment.
Design dell'applicazione stateless
La statelessness è il principio di design più importante per le applicazioni web scalabili orizzontalmente. Un'istanza di applicazione stateless non conserva dati per utente, per sessione o per richiesta in memoria tra le richieste. Ogni richiesta HTTP in arrivo porta tutto il contesto necessario per elaborarla — tipicamente tramite un JWT o un token di sessione — e può essere instradata verso qualsiasi istanza disponibile senza effetti collaterali.
Al contrario, un'applicazione con stato memorizza i dati di sessione in memoria su un server specifico. Una volta che un utente accede e la sua sessione risiede sul Server A, ogni richiesta successiva deve essere instradata verso il Server A — un pattern chiamato sticky sessions. Le sticky sessions limitano fondamentalmente le opzioni di scaling.
Il percorso verso la statelessness implica spostare tutto lo stato mutabile dalla memoria dell'applicazione a store esterni condivisi:
- Dati di sessione. Sostituite gli oggetti di sessione in memoria con JWT firmati o un session store distribuito come Redis. I JWT sono autonomi e non richiedono storage lato server; le sessioni Redis sono centralizzate e possono essere invalidate lato server.
- File caricati e generati. Non scrivete mai file sul disco locale di un server applicativo. Archiviateli in un object storage (AWS S3, Google Cloud Storage, Azure Blob) e referenziateli tramite URL.
- Cache applicativi. Le cache in-process (HashMap locale, cache LRU) creano stato divergente tra le istanze. Promuovetele in una cache distribuita condivisa (Redis, Memcached) affinché tutte le istanze condividano una visione coerente dei dati cachati.
- Stato WebSocket e in tempo reale. Le connessioni WebSocket sono intrinsecamente stateful. Utilizzate un livello pub/sub (Redis Pub/Sub, adattatore socket.io, Ably) per trasmettere eventi a tutte le istanze.
Strategie di load balancing
Un load balancer è il punto di ingresso a un cluster scalato orizzontalmente. Accetta le connessioni in arrivo e le instrada verso le istanze backend disponibili secondo un algoritmo scelto. La scelta dell'algoritmo, la configurazione degli health check e la policy di connection draining influenzano significativamente le prestazioni e l'affidabilità sotto pattern di carico reali.
Gli algoritmi più utilizzati nelle applicazioni web in produzione:
- Round Robin. Le richieste vengono distribuite sequenzialmente tra le istanze. Distribuzione statisticamente uguale in teoria; in pratica, le richieste lente possono causare squilibri. Funziona bene quando il tempo di elaborazione delle richieste è approssimativamente uniforme.
- Least Connections. Ogni nuova richiesta va all'istanza con il minor numero di connessioni attive in quel momento. Significativamente migliore del round robin quando la durata delle richieste varia ampiamente.
- Routing ponderato. Alle istanze vengono assegnati pesi in base alla loro capacità. Utile per deployment con istanze miste o durante canary release.
- IP Hash / Sticky Routing. Lo stesso IP client raggiunge sempre la stessa istanza. Utile per backend con stato — da evitare come strategia a lungo termine.
Il connection draining è altrettanto importante: quando un'istanza viene rimossa dal pool (durante un deployment o uno scale-down), il load balancer dovrebbe aspettare che le richieste in corso vengano completate prima di terminare la connessione — tipicamente 30–60 secondi.
Livelli di cache: dall'in-process al CDN
Il caching è l'intervento di performance ad alta leva più disponibile per un ingegnere backend web. Un singolo cache hit evita una query al database che potrebbe richiedere 5–50 ms; alla scala, il risparmio cumulativo è enorme.
Pensate al caching come a una pila di livelli, ciascuno progressivamente più veloce e progressivamente meno durevole:
- Cache CDN edge. Gli asset completamente statici (immagini, bundle JS, CSS, font) dovrebbero essere cachati all'edge del CDN e serviti dal point of presence più vicino all'utente. Un CDN ben configurato assorbe il 60–95% di tutte le richieste HTTP prima che raggiungano il vostro origin.
- Cache reverse proxy / gateway. Un livello NGINX o Kong può cachare le risposte API per brevi TTL (1–60 s) quando l'endpoint è sicuro da cachare.
- Cache in-memory distribuita (Redis / Memcached). Condivisa tra tutte le istanze applicative, utilizzata per aggregati calcolati, risultati di query del database, risposte di API terze e token di sessione. Redis è lo standard industriale nel 2026.
- Cache in-process applicativa. Una piccola cache LRU in-process per dati veramente caldi e raramente cambianti — ad esempio, una tabella di lookup con 200 codici paese caricata all'avvio.
Il tasso di hit della cache è la metrica chiave. Per una cache distribuita, puntate a un tasso superiore all'80% sugli endpoint più trafficati. Al di sotto del 70%, i vostri TTL sono probabilmente troppo brevi, le vostre chiavi di cache troppo granulari, o il vostro dataset ha hot spot in lettura reali che necessitano di un pattern di accesso diverso.
Elaborazione asincrona e code di messaggi
HTTP è un protocollo sincrono: il client aspetta mentre il server elabora la richiesta. Molte operazioni backend non devono tuttavia completarsi in modo sincrono: inviare un'email, ridimensionare un'immagine caricata, generare un report PDF, indicizzare un nuovo record in un motore di ricerca, chiamare un webhook lento di terze parti.
Il pattern è semplice: il vostro server web riceve la richiesta, persiste lo stato minimo richiesto, restituisce immediatamente HTTP 202 Accepted, e un processo worker separato esegue il lavoro pesante in modo asincrono.
Le opzioni di code di messaggi più utilizzate in produzione nel 2026:
- Amazon SQS. Completamente gestito, zero overhead operativo, scala automaticamente. La scelta predefinita nativa AWS per i team su infrastruttura AWS.
- RabbitMQ. Maturo, self-hosted o gestito (CloudAMQP). Supporta topologie di routing sofisticate (exchange, binding, dead-letter queue). Preferito quando è necessario un routing fine dei messaggi o code di priorità.
- Apache Kafka. Basato su log, alta velocità effettiva, progettato per l'event streaming a milioni di messaggi al secondo. La scelta giusta per event sourcing, pipeline analitiche ed elaborazione dati in tempo reale.
- Redis Streams / Bull Queue. Per i team già su Redis, Bull (Node.js) o RQ (Python) offrono semplici code di lavori con un overhead infrastrutturale minimo.
Scalabilità del database: replica in lettura e sharding
Il database è quasi sempre il primo collo di bottiglia in un'applicazione web in crescita. I server applicativi sono stateless e scalano orizzontalmente con attrito minimo; i database portano stato e sono significativamente più difficili da scalare. Fortunatamente, la grande maggioranza delle applicazioni web in produzione ha molte più letture che scritture — un rapporto di 80:20 o 90:10 è comune — il che significa che lo scaling in lettura è di solito il problema giusto da risolvere prima.
I replica in lettura sono il primo passo standard. La maggior parte dei servizi di database gestiti (AWS RDS, Google Cloud SQL, PlanetScale) supportano uno o più replica in lettura: copie sincronizzate del vostro database primario che accettano solo query in sola lettura. Un singolo replica ben provisionato può assorbire il 70–90% del carico di query.
Lo sharding del database è il passo successivo quando il throughput di scrittura o la dimensione del dataset supera ciò che un singolo primary può gestire. Lo sharding divide i vostri dati orizzontalmente su più nodi database (shard) in base a una chiave di shard. Lo sharding introduce una significativa complessità operativa e di query: le query cross-shard richiedono scatter-gather fan-out o ridondanza di dati denormalizzati.
| Tecnica | Risolve | Complessità | Quando usare |
|---|---|---|---|
| Ottimizzazione delle query | Query singole lente | Bassa | Sempre prima |
| Connection pooling | Esaurimento connessioni | Bassa | Connessioni > 100 |
| Replica in lettura | Throughput in lettura | Bassa-Moderata | Rapporto lettura:scrittura > 5:1 |
| Partizionamento tabelle | Dimensione tabella / performance query | Moderata | Tabelle > 50 M righe |
| Sharding | Throughput scrittura / dimensione totale dati | Alta | Quando i replica non sono sufficienti |
| DB distribuito gestito | Tutto quanto sopra | Moderata (ops esternalizzate) | Greenfield o budget di migrazione disponibile |
Autoscaling e pianificazione della capacità
L'autoscaling è il meccanismo attraverso cui la vostra infrastruttura regola automaticamente la capacità di calcolo in risposta alla domanda osservata — aggiungendo istanze durante i picchi di traffico e rimuovendole quando il carico diminuisce. Negli ambienti cloud (AWS Auto Scaling Groups, Google Cloud Managed Instance Groups, Kubernetes Horizontal Pod Autoscaler), l'autoscaling è l'approccio standard per mantenere la disponibilità senza over-provisionare la capacità di base per il carico di picco.
Segnali di scaling. Il segnale predefinito per la maggior parte degli autoscaler è l'utilizzo della CPU — aggiungere istanze quando la CPU media supera una soglia (tipicamente 60–70%), rimuovere quando scende al di sotto di una soglia inferiore (tipicamente 30–40%). Per le API web, la latenza delle richieste (tempo di risposta API p95) o il numero di connessioni attive sono spesso segnali più accurati.
Pianificazione della capacità. L'autoscaling non elimina la necessità della pianificazione della capacità — ne cambia la natura. Non è più necessario pre-provisionare per il carico di picco sostenuto, ma è necessario specificare numeri minimi e massimi di istanze che corrispondano ai requisiti di disponibilità e al budget. Un minimo di 2 istanze (una per zona di disponibilità) garantisce ridondanza; un massimo che copre il picco stimato più un buffer del 30% previene lo scaling incontrollato.
SLO e degradazione progressiva
La scalabilità non riguarda solo la gestione di più carico — si tratta di mantenere prestazioni accettabili man mano che il carico aumenta e di recuperare in modo ordinato quando i componenti falliscono. I Service Level Objective (SLO) sono gli impegni formali che definiscono "accettabile" per il vostro sistema.
Target SLO standard per le applicazioni web in produzione:
- Disponibilità: 99,9% (Tre Noni) consente 8,7 ore di downtime all'anno. Questa è l'aspettativa minima per qualsiasi SaaS commerciale. 99,95% (4,4 h/anno) è raggiungibile con deployment multi-AZ e failover automatico.
- Latenza API: p50 < 150 ms, p95 < 500 ms, p99 < 1.000 ms per le API HTTP sincrone.
- Tasso di errore: Meno dello 0,1% di tutte le richieste dovrebbe restituire errori 5xx durante il funzionamento normale.
- Throughput: Definite il vostro RPS di picco atteso e progettate la vostra infrastruttura per sostenerlo ai target di latenza SLO con il 30–50% di margine.
La degradazione progressiva è il principio di design che garantisce che la vostra applicazione si degradi in modo prevedibile sotto sovraccarico piuttosto che fallire completamente:
- Circuit breaker. Se le chiamate a un servizio esterno falliscono ripetutamente, aprite il circuito — cortocircuitate le chiamate successive con una risposta di fallback cachata o un messaggio "servizio temporaneamente non disponibile".
- Load shedding. Quando le code di richieste superano una soglia, rifiutate attivamente le richieste a bassa priorità con HTTP 429 piuttosto che accettarle in una coda che non può mai svuotarsi.
- Feature flag per percorsi non critici. Le funzionalità non essenziali possono essere disabilitate a livello di feature flag durante un incidente ad alto carico.
- Dati non aggiornati anziché nessun dato. In caso di cache miss con un database lento o non disponibile, servire una risposta cachata leggermente non aggiornata è spesso preferibile a restituire un errore.
I servizi di sviluppo di applicazioni web che offriamo a YuSMP Group includono revisione architetturale e pianificazione della scalabilità in ogni engagement. Il nostro articolo complementare su monolite vs microservizi affronta come le scelte di decomposizione interagiscono con la strategia di scaling.
FAQ
Qual è la differenza tra scaling orizzontale e verticale?
Lo scaling verticale (scale-up) consiste nell'aggiungere più CPU, RAM o storage a un singolo server. È rapido da implementare ma ha un limite massimo e crea un single point of failure. Lo scaling orizzontale (scale-out) consiste nell'aggiungere più istanze server e distribuire il carico tra esse. Non ha limiti teorici, elimina i single point of failure ed è il fondamento dell'architettura cloud-native.
Perché la statelessness è importante per la scalabilità delle app web?
Un'applicazione stateless non memorizza dati di sessione per richiesta in memoria su nessuna singola istanza. Ogni istanza può gestire qualsiasi richiesta in arrivo senza coordinazione. Ciò consente di aggiungere o rimuovere istanze istantaneamente — prerequisito per l'autoscaling e i deployment senza downtime. I server con stato richiedono sticky sessions, complicando il load balancing e impedendo la vera scala orizzontale.
Quando dovrei aggiungere un livello di cache alla mia app web?
Aggiungete cache quando il traffico in lettura domina — tipicamente quando il CPU del database supera il 60% o quando gli stessi dati vengono letti molto più spesso di quanto cambino. Una cache Redis ben configurata può assorbire l'80–95% del carico in lettura, riducendo proporzionalmente la pressione sul database.
Cos'è il sharding del database e quando è necessario?
Il sharding divide una tabella di database orizzontalmente su più server (shard) in base a una chiave di shard. È necessario quando il dataset supera la capacità di un singolo server database o quando il throughput di scrittura supera ciò che un singolo primary può gestire. La maggior parte dei team ricorre prima ai replica in lettura e introduce lo sharding solo a scala significativa, tipicamente 10 M+ righe con alti tassi di scrittura.
Come migliorano le code di messaggi la scalabilità delle app web?
Le code di messaggi (RabbitMQ, SQS, Kafka) disaccoppiano il lavoro lento o ad alto consumo di risorse dal ciclo di richiesta HTTP. Invece di far attendere l'utente mentre il server invia email, ridimensiona immagini o chiama API di terze parti, il task viene accodato e viene restituito immediatamente HTTP 202. I worker svuotano la coda indipendentemente e possono essere scalati separatamente dal tier web.
Quali SLO dovrebbe raggiungere un'app web scalabile?
Target standard per un SaaS in produzione: disponibilità 99,9% (budget di 8,7 ore/anno di downtime), latenza API p50 < 150 ms, p95 < 500 ms, p99 < 1.000 ms. Il tasso di errore deve rimanere inferiore allo 0,1% di tutte le richieste. Per le app consumer, restringete il p95 a meno di 300 ms e monitorate i Core Web Vitals (LCP < 2,5 s, INP < 200 ms).
Pubblicato il 13 giugno 2026. Le raccomandazioni tecniche riflettono i pattern di produzione osservati nei progetti clienti YuSMP Group 2023–2026 e la documentazione aggiornata dei provider cloud.


