English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ospite
1 / ?
torna alle lezioni

Due modi per gestire più carico

Benvenuti

Quando un servizio inizia a cedere sotto il carico, un operatore si trova di fronte a una scelta. Rendere la macchina esistente più grande (più CPU, più RAM, dischi più veloci). Oppure aggiungere più macchine che ciascuna esegue lo stesso lavoro.

Il primo percorso è noto come scalabilità verticale (scale up). Il secondo è noto come scalabilità orizzontale (scale out).

Questa lezione spiega perché quasi ogni architettura web moderna sceglie la scalabilità orizzontale e quale proprietà del carico di lavoro rende fattibile questa scelta. La risposta si nasconde in una sola parola: stato.

Entro la fine avrai compreso:

- Le curve di costo dello scaling verticale rispetto a quello orizzontale e in quali casi ciascuno ha senso

- Cosa significano in pratica 'stateful' e 'stateless', e perché uno dei due si replica in modo economico

- La matematica per dimensionare una flotta di repliche sotto carico previsto e di picco

- La regola del margine di sicurezza che impedisce a un livello di collassare oltre il punto di inflessione della coda

- Dove deve risiedere lo stato (non scompare mai) e come spostarlo fuori dai livelli che necessitano di scaling

Perché lo Scaling Orizzontale Vince Oltre una Soglia

Scaling Verticale: Una Scatola Più Grande

Vantaggi: semplice. Nessuna modifica al codice. Nessuna coordinazione. Lo stesso processo ora dispone di più CPU.

Svantaggi: limite superiore. La VM commercialmente disponibile più grande ha una quantità finita di RAM e core. Oltre a quello, nessun denaro può acquistare più margine. I costi diventano superlineari oltre il punto ottimale dell'offerta di un vendor. Un guasto di quella singola macchina porta all'arresto dell'intero servizio.

Scalabilità Orizzontale: Molte Macchine Più Piccole

Vantaggi: nessun limite superiore (fino alla tua disponibilità a pagare e coordinare le macchine). La capacità si aggiunge linearmente con il numero di repliche, in modo prevedibile. Un guasto di una singola replica rimuove 1/N della capacità, non il 100%.

Svantaggi: richiede che il carico di lavoro lo supporti. Alcuni carichi di lavoro (un singolo grande database, un server di gioco con stato che mantiene sessioni attive) resistono alla scalabilità orizzontale. La coordinazione e la distribuzione del carico diventano preoccupazioni operative.

Il punto di svolta: qualsiasi servizio di produzione che deve sopravvivere al guasto di una singola macchina deve essere eseguito su almeno due macchine. Una volta accettate due, hai già scelto la scalabilità orizzontale. Da lì, la domanda non è "dovremmo farlo?" ma "quanto economicamente possiamo aggiungere la prossima replica?"

L'abilitatore chiave: un carico di lavoro che non mantiene stato per richiesta sulla macchina stessa. In tal caso, qualsiasi replica può rispondere a qualsiasi richiesta e l'aggiunta di una replica aggiunge capacità senza coordinazione.

Vertical vs horizontal scaling: cost curve & ceiling

Un team esegue un servizio su una singola VM con 8 CPU che gestisce 800 richieste al secondo al 60% di utilizzo CPU. Si prevede che il traffico crescerà di 4 volte nel prossimo anno. Stanno discutendo: noleggiare una VM da 32 CPU (scaling verticale) oppure eseguire quattro VM identiche da 8 CPU dietro un load balancer (scaling orizzontale). Quale opzione consigli e cita due motivi che vadano oltre la pura capacità.

Statoful vs Stateless nella pratica

Lo Stato Non Scompare, Si Sposta Semplicemente

Componente con stato: conserva informazioni la cui perdita modificherebbe il comportamento. Un database che contiene gli account degli utenti. Una cache che contiene i token di sessione. Un worker che vincola una connessione di streaming di lunga durata a un utente specifico.

Componente senza stato: non conserva informazioni la cui perdita sarebbe rilevante. Un livello web che legge una richiesta, interroga un database e scrive una risposta. Ogni richiesta è autonoma; il livello non ricorda nulla tra una richiesta e l'altra.

Insight chiave: lo stato non scompare mai da un sistema. Si sposta in un livello progettato per contenerlo (un database, un cluster Redis, un object store). I livelli che gestiscono il traffico possono quindi essere senza stato, e i livelli senza stato scalano orizzontalmente perché qualsiasi replica può rispondere a qualsiasi richiesta.

Test pratico: se si eliminasse casualmente un processo in questo livello e lo si riavviasse, qualche utente riscontrerebbe una risposta errata o una sessione persa? Se sì, il livello conserva stato. Se no, non lo conserva.

Esempi

- Un processo web Python che legge le richieste, interroga Postgres e restituisce JSON: senza stato. Lo stato risiede in Postgres.

- Un processo web Python che conserva i carrelli della spesa degli utenti nella memoria locale: con stato. L'eliminazione del processo comporta la perdita dei carrelli.

- Un server WebSocket che mantiene connessioni aperte con gli utenti della chat: statale nel senso della connessione. Terminare il processo interrompe le connessioni; i client devono ricollegarsi. Spesso questi sistemi possono comunque scalare in orizzontale con attenzione (sessioni appiccicose, hashing coerente).

- Una cache Redis che fa da front a Postgres: statale per quanto riguarda i contenuti della cache, ma accettabile se i mancati recuperi dalla cache (cache miss) sono tollerabili. Un guasto a una replica comporta un mancato recupero dalla cache, non una perdita di dati.

Progettare per la scalabilità orizzontale = spostare lo stato fuori dal livello che deve scalare.

Audit di un livello sospetto

Un team esegue un'API di raccomandazioni su 6 VM di backend dietro un proxy inverso. L'applicazione: legge un ID utente dalla richiesta, recupera le attività recenti dell'utente da Postgres, esegue un algoritmo di scoring e restituisce un elenco di elementi raccomandati. Due comportamenti non standard:

- L'applicazione mantiene una cache delle "attività recenti dell'utente" nella memoria di processo, popolata alla prima richiesta per un utente e riutilizzata nelle richieste successive.

- L'applicazione utilizza sessioni appiccicose (sticky sessions): una volta che un utente accede alla VM #3, tutte le sue richieste successive vengono inviate alla VM #3 (il proxy è configurato per il routing appiccicoso basato su un cookie).

Identifica quale di questi due comportamenti rende il livello statale e spiega cosa andrebbe in errore se il team provasse a scalare da 6 VM a 12. Quindi proponi una ridisegnazione che consenta al livello di scalare in orizzontale senza perdere il vantaggio della cache utente.

La Formula delle Repliche

La Formula di Capacità più Semplice

Una volta che un tier diventa stateless, dimensionarlo diventa un'operazione aritmetica. Hai bisogno di un numero sufficiente di repliche affinché il carico in stato stazionario arrivi e parta allo stesso tasso, con un margine per i picchi.

La formula:

replicas = ⌈ (peak_load × surge_factor) / per_replica_capacity ⌉ + headroom

Dove:

- peak_load: massimo numero di richieste al secondo sostenuto che ci si aspetta in condizioni operative normali

- surge_factor: un moltiplicatore che copre brevi picchi al di sopra del picco massimo (comunemente 1,5x a 2x per traffico prevedibile, 3x o più per traffico virale / imprevedibile)

- per_replica_capacity: richieste al secondo che una singola replica gestisce con latenza e utilizzo accettabili (comunemente misurato al 70% di CPU, non a saturazione)

- headroom: repliche aggiuntive affinché il fallimento di alcune repliche non crolli l'intero tier (comunemente 1-2 repliche per flotte piccole, 10-20% per quelle più grandi)

Esempio risolto: un backend gestisce 100 req/s al 70% di CPU per replica. Il carico di picco è di 600 req/s. Ci si aspettano occasionali picchi di 2x. Si desidera sopravvivere a 2 guasti di replica.

replicas = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14 replica

La regola dell'80%

La capacità per replica non è il punto di saturazione. Misurare la capacità al 70-80% di CPU, mai al 100%.

Oltre circa l'80% di utilizzo, le curve di code salgono bruscamente: una coda che funzionava in 10 ms al 60% di utilizzo funziona in 80 ms al 90% di utilizzo. La latenza, non la larghezza di banda, si rompe per prima. (La lezione correlata geometry_of_stateless_horizontal_scaling deriva questa curva matematicamente.)

Autoscaling vs Provisioning Statico

Statico: provisionare per il picco × margine di picco e accettare il costo di funzionamento a bassa utilizzazione al di fuori dei picchi.

Autoscaling: un controller aggiunge e rimuove le replica in base all'utilizzo osservato, alla latenza target o alla profondità della coda.

Avvertenza sull'autoscaling: il tempo di avvio a freddo è fondamentale. Se un nuovo replica richiede 2 minuti per avviarsi, l'autoscaling non può rispondere a un picco di 30 secondi. Un autoscaling maturo mantiene un pool caldo di replica pre-provisionati appena sotto la soglia di scale-up.

Formula per la dimensionamento delle replica con esempio pratico

Dimensiona una flotta per un nuovo servizio

Il tuo team prevede di lanciare un'API per i metadati video. I benchmark mostrano che una singola replica gestisce 250 req/s con un utilizzo CPU del 70% e una latenza p99 di 50 ms. Il marketing prevede un carico di punta di 4.000 req/s durante le ore di punta. Un evento promozionale pianificato potrebbe causare un picco di 3 volte il carico di punta per un breve periodo. Si desidera che il servizio sopravviva a 3 guasti simultanei delle replica senza superare l'80% di utilizzo sulle replica sopravvissute.

Applica la formula delle replica per dimensionare la flotta di lancio. Mostra il tuo lavoro passo dopo passo, & poi spiega un motivo per cui il tuo numero potrebbe comunque essere errato (avvio a freddo, riscaldamento della cache, latenza delle dipendenze, qualsiasi cosa tu possa difendere).

Avvio a Freddo, Scarico Lento e Altri Limiti Reali

Le Flotte Reali Hanno Limiti Reali

La formula presuppone che i replica appaiano istantaneamente, accettino il traffico istantaneamente e lo smaltiscano istantaneamente. Nessuna di queste condizioni è vera in produzione.

Avvio a freddo (Cold start): un nuovo replica ha bisogno di avviare il sistema operativo, avviare il processo, caricare la configurazione, riscaldare le cache e superare i controlli di salute. Questo può richiedere da 5 secondi (riavvio del container) a 5 minuti (avvio completo della VM + pull dell'immagine). L'autoscaling non può rispondere a picchi più brevi di questo ritardo.

Scarico lento (Slow drain): un replica che viene rimosso dalla pool ha bisogno di tempo per completare le richieste in corso prima di terminare. Altrimenti, gli utenti vedranno risposte troncate. I proxy inversi supportano lo scarico (smettere di accettare nuove richieste, completare quelle attive), ma richiede da secondi a minuti.

Pool caldo: le flotte di produzione mantengono un pool di repliche pre-provisionate ma inattive, pronte ad accettare traffico su segnale. Scambia un piccolo costo costante per una risposta rapida ai picchi di carico.

Connection draining vs kill immediato: lo spegnimento graduale è importante. Un SIGTERM che attiva il drain richiede più tempo di un SIGKILL, ma non interrompe le richieste degli utenti.

Finestra di health check: una replica appena avviata potrebbe superare il suo primo health check prima che il pool di connessioni al database sia caldo; il proxy invia quindi traffico reale e le prime dozzina di richieste sono lente. Configura gli health check per testare il percorso reale, non solo la vitalità del processo.

Creep di stickiness: anche i livelli nominalmente stateless acquisiscono stickiness nel tempo (cache CDN, cache dei resolver DNS, pool di connessioni). Sii scettico verso le "repliche identiche" che tuttavia si comportano in modo diverso.

Pool caldo o Autoscaling reattivo?

La tua API di metadati video (la stessa della domanda precedente, dimensionata con 51 repliche per il picco stabile + picchi improvvisi) subisce un picco di 30 secondi fino a 5 volte il carico normale ogni volta che viene caricato un nuovo video virale. L'autoscaling attualmente richiede 90 secondi per aggiungere una nuova replica da freddo (pull dell'immagine + warmup). Durante l'intervallo di 90 secondi, la latenza sale bruscamente e alcune richieste scadono (timeout).

Proponi una soluzione. Scegli: (a) mantenere l'autoscaling reattivo ma configurarlo diversamente, (b) provisionare un pool caldo di repliche inattive, o (c) provisionare staticamente per il picco 5x in modo permanente. Giustifica la tua scelta e indica un costo che le altre due opzioni imporrebbero.

Progettare un livello stateless sotto vincoli

Sintesi

Hai imparato perché lo scaling orizzontale vince oltre una certa soglia, cosa significa "stato" in pratica, come dimensionare una flotta in base al carico atteso e di picco, e dove lo scaling orizzontale si rompe ai margini.

Applica tutti e quattro.

Progetta un tier backend per feed.example.com, un'API per feed social. Vincoli: capacità per replica di 200 req/s al 70% di CPU; carico di picco atteso di 1500 req/s; fattore di picco (surge) di 2,5x (storie occasionalmente in tendenza); sopravvivere a 2 guasti simultanei di replica; tempo di avvio a freddo (cold-start) di 60 secondi; i picchi possono durare 45 secondi; il budget consente una certa capacità inattiva, ma non una provisioning permanente di 2,5x.

Dimensiona la flotta in regime stazionario (mostra i calcoli), scegli la tua strategia per la gestione dei picchi (pool caldo / autoscaling reattivo / entrambi) e identifica un elemento di stato per utente che l'applicazione probabilmente detiene e dove deve essere spostato per mantenere il tier stateless.

Dove va questo corso da qui in avanti

Dove porterà questo corso

Ora disponi di un modello mentale funzionante per un livello stateless: perché scala, come dimensionarlo, cosa si rompe ai suoi limiti e dove deve spostarsi lo stato quando lo si spinge oltre il livello che necessita di crescere.

La prossima lezione di questo corso (cs_distsys_ingress_egress_separation) affronta un problema più sottile: anche un livello stateless dimensionato in modo perfetto può fallire in modi inaspettati quando il traffico in entrata e in uscita condivide lo stesso percorso di rete. L'esempio classico coinvolge un proxy che tenta di connettersi a sé stesso; la soluzione consiste nel dividere un unico livello in due, con responsabilità diverse.

Lezione complementare: geometry_of_stateless_horizontal_scaling deriva la curva di code, applica la legge di Little a una flotta di repliche e spiega il significato geometrico della ginocchia di utilizzo all'80%.

Ben fatto. Avanti.