I tre concetti di guasto da conoscere
Benvenuto
I sistemi distribuiti falliscono in modo prevedibile. Una volta appresi questi schemi, ogni postmortem diventa un esercizio di riconoscimento anziché un mistero.
Tre concetti coprono la maggior parte di ciò che conta nell'analisi dei guasti in produzione:
Punto singolo di guasto (SPOF): un componente il cui fallimento porta al crollo di un sistema più ampio. Spesso nascosto: il server DNS su cui tutti dipendono; il certificato contro cui tutto viene rinnovato; il master del database unico.
Cascading failure: il guasto di un componente ne innesca un altro, che a sua volta ne innesca un altro ancora. Un database lento causa timeout nel livello API, che causano retry, che caricano ulteriormente il database, che causano più timeout. L'onda d'urto si diffonde.
Blast radius: quanta parte del sistema va in arresto quando un componente fallisce. Le scelte architetturali limitano o non limitano il raggio d'azione. Un SPOF ha un raggio d'azione illimitato. Un servizio compartimentato (bulkhead) ha un raggio d'azione limitato.
Entro la fine di questa lezione sarai in grado di:
- Identificare gli SPOF in un'architettura tramite ispezione
- Riconoscere i pattern di guasto a cascata: thundering herd, retry storm, queue of death
- Leggere una timeline reale e separare il trigger dal difetto latente che il trigger ha fatto emergere
- Scrivere voci d'azione senza attribuire colpe (blameless) che puntino ai sistemi invece che alle persone, coprendo prevenzione / rilevamento / recupero
- Ragionare su bulkhead e circuit breaker come strumenti per limitare il raggio d'azione
Individua il Punto Singolo di Guasto
Ispezione dell'architettura a strati
Considera una piccola architettura web:
- DNS: api.example.com -> singolo IP del nameserver 203.0.113.10 ospitato da un unico provider DNS
- CDN: singolo vendor CDN davanti a api.example.com
- Ingress: due macchine reverse proxy dietro un load balancer
- Backend: sei repliche API in due zone di disponibilità (tre per zona)
- Database: un primario + una replica in sola lettura, nella stessa zona di disponibilità
- Cache: cluster Redis, tre nodi distribuiti nelle stesse due zone di disponibilità
Domanda: quali componenti sono SPOF? Suggerimento: gli SPOF non sono sempre del tipo ovvio "singola macchina". Un cluster di tre macchine tutte in un'unica zona di disponibilità è un SPOF per il guasto di quella zona.
Tre Pattern di Cascata Classici
I Guasti Si Propagano Attraverso le Dipendenze
Pattern 1: Thundering herd (Sciame di richieste). Una risorsa condivisa (cache, lock, database) va in errore o viene riavviata. Ogni client che dipendeva da essa ripete la richiesta simultaneamente. L'ondata di traffico sovraccarica il servizio appena tornato online; le richieste di ripetizione si accumulano più velocemente di quanto il recupero possa assorbirle; il recupero non si completa mai.
Pattern 2: Retry storm (Tempesta di ripetizioni). Un servizio a valle rallenta. I chiamanti a monte, invece di fallire, ripetono le richieste. Le ripetizioni moltiplicano il carico originale. Il servizio lento rallenta ulteriormente, innescando altre ripetizioni. Alla fine, il carico supera la capacità persino di una versione sana del servizio.
Pattern 3: Queue of death (Coda della morte). Una coda di elaborazione senza backpressure riceve messaggi più velocemente di quanto li elabori. La coda cresce senza limiti. La memoria si esaurisce; il consumer va in crash; viene riavviato; trova una coda ancora più grande; va in crash di nuovo.
Fil rouge (Elemento comune): una piccola perturbazione iniziale innesca un ciclo di retroazione positiva. La risposta stessa del sistema amplifica il guasto anziché attenuarlo.
Meccanismi di Smorzamento
Backoff esponenziale con jitter. I client che riprovano attendono tempi sempre più lunghi, con un offset casuale. Impedisce onde di retry sincronizzate.
Circuit breaker. Un chiamante traccia il tasso di fallimento a valle. Superata una soglia, il chiamante smette di effettuare chiamate per un periodo di raffreddamento e fallisce immediatamente le proprie richieste. Impedisce lavoro sprecato e consente al sistema a valle di recuperare.
Bulkhead. Isolare le risorse per dipendenza. Pool di connessioni A per il database, pool di connessioni B separato per la cache. Un database lento non può esaurire tutte le connessioni; le chiamate alla cache continuano.
Load shedding. Quando sovraccarico, scartare le richieste al bordo invece di accettarle e fallire lentamente. Un 429 in 1 ms è meglio di un 500 in 30 secondi.
Backpressure. Rallentare i produttori quando i consumatori non riescono a stare al passo. Le code diventano limitate; i mittenti vengono bloccati; la fonte originale del lavoro risente dell'attrito.
Diagnosi di una cascata
Il livello API di un team crolla durante un failover di routine del database. Cronologia:
- 14:00:00 — l'operatore promuove il database standby. Disponibilità non attesa prevista: ~10 secondi.
- 14:00:08 — il database primario non è disponibile. Le richieste del livello API iniziano a fallire con errori di connessione al database.
- 14:00:08 — il livello API esegue nuovi tentativi (configurazione predefinita: 5 tentativi, nessun backoff, intervallo di 100 ms).
- 14:00:11 — lo standby viene promosso e accetta nuove connessioni.
- 14:00:11 — il livello API apre migliaia di nuove connessioni al database simultaneamente (ogni replica × ogni richiesta concorrente × ogni tentativo).
- 14:00:13 — la pool di connessioni del nuovo primario è esaurita; le nuove connessioni vengono rifiutate.
- 14:00:13-14:05:00 — le repliche del livello API esauriscono le pool di connessioni, generano eccezioni, si bloccano, si riavviano e il ciclo si ripete.
- 14:05:00 — l'operatore arresta manualmente il traffico del livello API; il database si stabilizza.
- 14:10:00 — completamento della graduale ripristinazione del traffico. Interruzione totale: ~10 minuti (contro i ~10 secondi previsti).
DNS SERVFAIL: Due difetti sovrapposti
Un postmortem dalla forma reale
Di seguito è riportata una versione sanificata di un incidente reale. I nomi dei vendor sono stati modificati, gli IP sono stati anonimizzati; la forma, la cronologia e le lezioni apprese sono reali.
Riepilogo
Il sito example.com ha restituito SERVFAIL da tutti i resolver DNS pubblici per circa 3-4 ore. Tutte le altre 46 zone sullo stesso master DNS non sono state interessate. Causa radice: due difetti sovrapposti.
1. Il Vendor A (un provider DNS secondario) ha aggiunto un nuovo IP di sincronizzazione interna che non era presente nell'elenco di autorizzazione allow-axfr-ips del primario.
2. La zona example.com presentava un conflitto CNAME che violava la RFC da anni (demo.example.com aveva sia record CNAME che record MX/TXT sulla stessa etichetta), il che ha causato al Vendor A il rifiuto della zona durante un nuovo AXFR.
Cronologia (UTC)
- ~15:00 — Il Vendor A aggiunge il nuovo IP di sincronizzazione 198.51.100.42 alla propria infrastruttura
- 15:02 — Il primo AXFR-out denied per 198.51.100.42 appare nei log del DNS primario (nessun allarme su questo segnale)
- ~18:00 — Raggiunta la finestra di scadenza SOA; il Vendor A rimuove la zona example.com dalla cache
- ~18:30 — Rilevato SERVFAIL esternamente
- ~19:45 — Causa radice identificata
- 20:00 — 198.51.100.42 aggiunto a allow-axfr-ips; primario riavviato
- 20:05 — Inviato NOTIFY; avviato AXFR; la zona è ANCORA in stato SERVFAIL (conflitto CNAME)
- 20:07 — check-zone rivela 1 errore: conflitto CNAME su demo.example.com
- 20:09 — CNAME sostituito con record A; verifica della zona pulita (0 errori)
- 20:10 — Inviato NOTIFY; AXFR completato; il Fornitore A inizia a servire la zona
- 20:11 — dig @8.8.8.8 example.com A restituisce l'IP corretto — RISOLTO
Perché solo example.com?
Tutte le 47 zone condividono lo stesso DNS primario. Il blocco IP di AXFR ha interessato tutte le zone. Tuttavia, solo example.com presentava il conflitto CNAME e solo example.com necessitava di un nuovo AXFR nel momento in cui è stato applicato il divieto. Le altre zone avevano già aggiornato i dati prima del divieto o non ne avevano ancora bisogno.
Difetto latente
Il conflitto CNAME su demo.example.com esisteva da anni. Funzionava perché il primario serviva la zona dal suo database (tollerante verso le violazioni RFC) e il Fornitore A serviva da dati in cache obsoleti antecedenti l'introduzione della violazione. Quando il Fornitore A ha scartato la cache e ha avuto bisogno di dati freschi, la violazione è emersa.
Innesco
Il Fornitore A ha aggiunto silenziosamente un nuovo IP di sincronizzazione. L'elenco di autorizzazioni del primario non lo includeva. AXFR negato. Tre ore dopo (scadenza SOA), il Fornitore A ha eliminato la zona. Il difetto latente è emerso quando il sistema ha tentato di recuperare.
Scrivere voci d'azione senza attribuire colpe
Senza colpe: puntare ai sistemi, non alle persone
Una voce d'azione senza attribuire colpe indica qualcosa che il sistema dovrebbe fare in modo diverso, non qualcosa che una persona dovrebbe fare in modo diverso. "Formare l'operatore" attribuisce la colpa. "Aggiungere un controllo automatico che rilevi questo problema prima del deploy" non attribuisce la colpa.
Le buone voci d'azione senza attribuire colpe si raggruppano in tre dimensioni:
- Prevenzione: rendere più difficile o impossibile che succeda la cosa negativa
- Rilevamento: accorgersene prima se succede
- Recupero: limitare i danni quando succede
Ogni voce deve indicare (1) la specifica modifica al sistema, (2) il team responsabile e (3) la dimensione a cui si riferisce.
Compartimenti che affondano senza la nave
Prestito dall'ingegneria navale
Le navi utilizzano paratie stagne: pareti verticali che dividono lo scafo in compartimenti. Un compartimento può allagarsi senza far affondare la nave; un altro può guastarsi senza influenzare il resto.
I sistemi distribuiti prendono in prestito la stessa parola e la stessa idea.
Pattern Bulkhead: isolare le risorse per dipendenza. Un servizio che chiama tre API a valle utilizza tre pool di connessioni separati, tre pool di thread separati, tre budget di retry separati. Un downstream lento o in errore non può consumare le risorse allocate per gli altri due.
Senza bulkhead: una dipendenza lenta esaurisce il pool di thread condiviso; le chiamate ad altre dipendenze restano in attesa di thread; l'intero servizio diventa non reattivo.
Con bulkhead: una dipendenza lenta esaurisce il proprio pool; le chiamate ad essa falliscono rapidamente; le chiamate ad altre dipendenze continuano normalmente; il raggio d'azione dell'impatto (blast radius) rimane limitato alla dipendenza in errore.
Circuit Breaker
Pattern Circuit Breaker: un wrapper con stato attorno a una dipendenza a valle che traccia il tasso di fallimento. Tre stati:
- Chiuso (normale): le chiamate passano attraverso. I fallimenti vengono conteggiati.
- Aperto (scattato): superato una soglia di fallimento (ad esempio, il 50% di fallimenti negli ultimi 30 secondi), il circuito si apre. Le chiamate falliscono immediatamente senza tentare la dipendenza. Risparmia al chiamante lavoro sprecato; protegge la dipendenza dal ricevere carico mentre è non sana.
- Mezzo-aperto (test): dopo un periodo di raffreddamento, il circuito lascia passare una piccola frazione di chiamate. Se hanno successo, torna a chiudersi in modalità normale. Se falliscono, si riapre per un altro periodo di raffreddamento.
L'idea chiave: il circuit breaker previene lo sforzo sprecato durante periodi noti di malfunzionamento, & dà alla dipendenza a valle la possibilità di riprendersi senza un carico continuo.
I Bulkheads limitano il raggio d'azione dell'esplosione. I Circuit Breakers impediscono che l'esplosione si autoalimenti.
Limitare il raggio d'azione dell'esplosione
Il tuo servizio API chiama quattro servizi a valle: User Service, Recommendation Service, Notification Service, & un API di pagamento di terze parti. Il team ha sentito che 'il Recommendation Service è stato un po' instabile' & vuole assicurarsi che quando fallisce, il resto del sistema rimanga sano.
Oggi il servizio utilizza un unico pool di thread condiviso da 200 thread e un unico pool di connessioni HTTP condiviso. Tutti e quattro i servizi a valle competono per queste risorse. Non sono presenti circuit breaker.
Progetta una revisione dei modi di guasto
Sintesi
Hai imparato a individuare i SPOF (Single Point of Failure) tramite ispezione, a riconoscere i pattern di guasto a cascata, a distinguere il trigger dal difetto latente quando si legge un postmortem, a scrivere voci d'azione senza colpevolizzazioni che coprano prevenzione / rilevamento / recupero, e a limitare il raggio d'azione del guasto (blast radius) con bulkhead + circuit breaker + degradazione graduale.
Applica tutte e cinque le competenze.
Il tuo team sta lanciando un nuovo servizio search.example.com che dipende da tre servizi a valle: un indice di ricerca primario (index.example.com), un servizio di analytics (analytics.example.com) e un servizio di raccomandazioni (recs.example.com). Il team vuole che tu guidi una "revisione dei modi di guasto" prima del lancio.
Dove porterà questo corso
Dove porterà questo corso
Ora sei in grado di individuare un SPOF, riconoscere una cascata, leggere un postmortem in modo produttivo, scrivere voci d'azione senza attribuire colpe e limitare il raggio d'impatto per progettazione.
La lezione finale di questo corso (cs_distsys_observability_and_capacity) insegna cosa misurare per scoprire che un problema sta accadendo prima che lo facciano gli utenti. Controlli di stato, endpoint di versione, i quattro segnali d'oro a livello di proxy e come le decisioni sulla capacità di picco si ricollegano ai dati osservati.
Lezione complementare: geometry_of_failure_modes_and_blast_radius deriva la centralità di intermediazione (quale nodo del grafo è il collo di bottiglia) e il taglio minimo (il limite del raggio d'impatto).
Ben fatto. Avanti.