Architettura di dati e sistemi, dalle fondamenta Lezione 30 / 80

Split brain: cos'è e perché rovina tutto

La network partition in cui entrambe le metà di un cluster credono di essere il leader. Perché il quorum è l'unica difesa affidabile.

Due lezioni di partitioning e sharding alle spalle, questa parla della failure mode che vive silenziosamente sotto tutto il resto. Il cluster ha un leader. Il leader sta replicando ai follower. La rete in gran parte funziona. Poi smette. Uno switch si guasta, una routing table si aggiorna male, un cavo viene staccato dal rack sbagliato, un bug software nel network fabric isola una zone dalle altre. Il cluster è partizionato: non data partitioned, ma network partitioned. Alcuni nodi possono parlare tra loro e non con il resto. Il leader sta da una parte, alcuni follower stanno dall’altra, e quello che succede dopo è il tema della lezione.

Il nome di copertina per il caso peggiore è split brain. È il nome della situazione in cui entrambe le metà del cluster credono di essere al comando, entrambe accettano write, ed entrambe finiscono con uno stato che l’altra metà non ha. Quando la rete si ricompone e le due metà provano a fondersi, il database ha storie divergenti, e non c’è un modo automatico di riconciliarle senza perdere dati o violare invarianti. Questa lezione parla di come avviene lo split brain, perché è in modo unico catastrofico tra le failure mode, e dei pochi meccanismi che lo prevengono in modo affidabile.

Lo scenario

Considera un cluster a tre nodi: un leader, L, e due follower, F1 e F2. I client mandano write al leader; il leader replica ai follower; tutti felici.

Ora una network partition taglia il cluster: L è isolato da una parte; F1 e F2 stanno insieme dall’altra. Dal punto di vista di F1 e F2, il leader è diventato silenzioso. Mancano gli heartbeat. Aspettano il loro election timeout, decidono che il leader è morto, ed eleggono F1 come nuovo leader. Dal loro lato della partition, è esattamente quello che dovevano fare.

Dal punto di vista di L, sono i follower a essere diventati silenziosi. L sta ancora ricevendo heartbeat da sé stesso e da qualunque client sia rimasto dal suo lato della rete. Cosa cruciale, L crede ancora di essere il leader. Sta ancora accettando write dai client. I client dal suo lato della partition stanno ricevendo risposte di successo alle loro richieste, completamente ignari del fatto che qualcosa stia andando storto.

Ora la rete si ricompone. F1 e F2, con F1 come nuovo leader, hanno una serie di write che L non ha. L ha una serie di write che F1 e F2 non hanno. Le due metà non disaccordano semplicemente sulla storia: ognuna ha una lista di write confermate che l’altra non ha mai visto, con chiavi sovrapposte, con valori in conflitto, senza alcun modo di fonderle senza scegliere quali write perdere.

sequenceDiagram
    participant Client1 as Client (left side)
    participant L as Leader L
    participant F1 as Follower F1
    participant F2 as Follower F2
    participant Client2 as Client (right side)
    Note over L,F2: Network partition isolates L
    Client1->>L: write A
    L->>L: accept A (still thinks leader)
    F1->>F2: heartbeat timeout
    F1->>F2: elect F1 as new leader
    Client2->>F1: write B
    F1->>F2: replicate B
    Note over L,F2: Partition heals
    L->>F1: hello, I am leader
    F1->>L: no, I am leader (term incremented)
    Note over L,F1: A and B are conflicting writes; merge is undefined

Questo è lo split brain. Entrambe le parti hanno fatto esattamente quello per cui erano state progettate; il sistema nel suo insieme ha prodotto uno stato che il database non riesce a riconciliare.

Perché lo split brain è catastrofico in modo unico

La maggior parte dei fallimenti dei sistemi distribuiti sono recuperabili. Un nodo crasha: lo riporti su, riproduci il log, sei aggiornato. Una replica resta indietro: la fai recuperare con lo streaming del log. Un leader muore: ne eleggi uno nuovo, il vecchio non stava più accettando write comunque. Persino i fallimenti Bizantini (in cui un nodo dice bugie) possono essere tollerati da protocolli progettati per loro.

Lo split brain è diverso. Entrambe le parti hanno accettato write che l’altra parte non ha visto. Entrambe le parti hanno detto ai loro client che quelle write erano andate a buon fine. I client sono andati avanti. Quando la partition si ricompone, il database ha due storie ugualmente valide che disaccordano sulle stesse chiavi, e nessun meccanismo per scegliere tra loro senza rompere promesse che sono già state fatte.

L’esempio illustrativo classico è il financial ledger. Un utente ha 100 dollari. L’utente emette un prelievo di 60 dollari verso il lato sinistro della partition; il leader di sinistra lo autorizza, il saldo è ora 40. L’utente, frustrato dalla risposta lenta da qualche parte, emette anche un prelievo di 60 dollari verso il lato destro della partition; il leader di destra, con una copia stale del saldo che mostra ancora 100, lo autorizza pure, il saldo è ora 40 dal suo lato. Quando la partition si ricompone, il database ha autorizzato 120 dollari di prelievi da un conto da 100 dollari. Non c’è alcun merge automatico che recuperi; la banca ha perso 20 dollari.

La stessa forma si applica a qualunque sistema con invarianti che dipendono da una vista globale dei dati: inventario (“esiste solo uno di questo articolo, non può essere venduto due volte”), unique constraint (“questo username è preso”), counter (“questa risorsa è stata acquisita da esattamente un client”), idempotency key (“questa operazione deve girare al massimo una volta”). Per tutte queste, due leader che accettano write in conflitto è la failure mode che viola la proprietà che il sistema doveva fornire.

Il quorum è l’unica difesa affidabile

La difesa, l’unica difesa affidabile, è richiedere che una maggioranza di nodi sia d’accordo prima che qualunque write possa essere accettata. Con tre nodi, te ne servono due per avere un quorum. Una partition 1-2: il lato con due nodi può ancora formare una maggioranza e accettare write; quello con uno solo no. Il nodo solitario vede di non poter raggiungere una maggioranza dei suoi peer, rifiuta di accettare write, e o resta inattivo o restituisce errori ai client. Non c’è possibilità che due leader accettino write simultaneamente, perché non c’è possibilità che due maggioranze esistano simultaneamente in una partition di un cluster a tre nodi.

È esattamente per questo che Raft e Paxos richiedono il voto di maggioranza per la leader election e per il commit del log (lezione 14). Il requisito di maggioranza non è una scelta di design arbitraria; è l’unica regola di quorum che rende lo split brain matematicamente impossibile. Due maggioranze qualsiasi di N nodi devono sovrapporsi in almeno un nodo, e quel nodo sovrapposto, con il suo “ho votato per questo leader” o “ho fatto commit di questa log entry”, impone una singola fonte di verità attraverso la partition.

La ricetta di deployment che ne deriva è dritta e facile da dimenticare sotto pressione di costi: gira con cluster di dimensione dispari, tre come minimo, cinque per qualunque sistema a cui tieni, sette se vuoi davvero sopravvivere a fallimenti multipli simultanei. Le dimensioni di cluster pari sono operativamente peggiori di quelle dispari. Un cluster a quattro nodi spaccato 2-2 non ha maggioranza da nessuna parte, quindi l’intero cluster è non disponibile per le write durante la partition. Andare da tre a quattro nodi non migliora la availability; la peggiora. Cinque nodi sopravvivono a due fallimenti simultanei e si partizionano in modo pulito in split 3-2 con una maggioranza dal lato più grande.

I cluster a due nodi sono il deployment che ti morde più spesso. Due nodi non hanno maggioranza quando sono partizionati: ogni lato ha esattamente un nodo, e uno non è una maggioranza di due. Quindi o accetti che il cluster sia non disponibile per le write durante qualunque partition (e tanto valeva girare un singolo nodo, che ha lo stesso profilo di availability e metà del costo operativo), oppure permetti a ogni nodo di agire per conto suo quando è partizionato, e ora hai appena progettato dentro uno split brain. Non girare cluster a due nodi per niente che conti. O giri un nodo solo e accetti onestamente il single-point-of-failure, oppure giri tre nodi e ottieni il vero beneficio di availability.

Fencing: la seconda linea di difesa

Il quorum previene due leader simultanei. Non previene che un leader creda brevemente di essere ancora il leader dopo aver perso il quorum. Lo scenario: un leader viene isolato dai suoi follower, i follower eleggono un nuovo leader, ma il vecchio leader non si è ancora accorto di essere partizionato e continua a mandare write ai servizi a valle. I servizi a valle non sanno nulla del consensus protocol; vedono write da un nodo che dichiara di essere il leader e le accettano.

La soluzione sono i fencing token. Il sistema di consenso allega un token monotonicamente crescente (spesso chiamato term, oppure epoch, oppure fencing number) a ogni concessione di leadership. Ogni write che il leader manda a un servizio a valle include questo token. I servizi a valle si ricordano il token più alto che abbiano mai visto, e rifiutano qualunque write che arrivi con un token più basso. Quando viene eletto il nuovo leader, il suo token è più alto di quello del vecchio leader. Quando il vecchio leader, ancora operando sulla sua convinzione stale, manda una write a un servizio a valle con il suo vecchio token, il servizio la rifiuta: “il tuo token è 7, ho già visto il token 8 da qualcun altro.” Il vecchio leader è fenced out.

Il fencing è la risposta giusta per il gap tra “ho perso il quorum” e “mi sono accorto di aver perso il quorum.” Copre anche il caso del leader rallentato: un leader si ferma per garbage collection o per uno swap-out per quindici secondi, il cluster decide che è morto e ne elegge uno nuovo, e quando il vecchio leader riprende prova a scrivere come se nulla fosse. Senza fencing, i servizi a valle accettano le sue write; con il fencing, le rifiutano.

Implementare il fencing richiede che i servizi a valle siano consapevoli del token, il che significa coordinare il fencing tra i servizi applicativi e le dipendenze esterne. Alcuni sistemi lo fanno bene; molti no. Quando leggi un postmortem del tipo “abbiamo perso dati a causa di uno split brain”, il layer mancante sono di solito i fencing token al confine dello storage o del servizio esterno.

Pattern di fallimento del mondo reale

I report degli incidenti specifici variano, ma le forme si ripetono attraverso molti anni e molti sistemi. I pattern che vale la pena riconoscere:

DNS-based failover con TTL troppo lungo durante una partition. Un primary database fallisce, il team di operations sposta un record DNS per puntare alla replica, e il traffico inizia a muoversi verso la replica. Ma i client hanno il vecchio record DNS in cache per la durata del TTL; continuano a parlare con il vecchio primary, che a quel punto torna online e ricomincia ad accettare write, mentre altri client si sono già spostati al nuovo primary. Due database, entrambi che accettano write, incollati insieme da un rollover DNS lento.

Virtual IP failover con uno split brain a livello di rete. Gli strumenti HA basati su heartbeat (Pacemaker, Keepalived, gli stack HA più vecchi) spostano un virtual IP da un primary fallito a uno standby. Se il link di heartbeat tra i due nodi fallisce ma la rete pubblica funziona ancora, entrambi i nodi possono decidere di essere il nodo attivo ed entrambi possono rivendicare lo stesso VIP. I client da ciascun lato della rete vedono “primary” diversi con lo stesso indirizzo.

Replicazione asincrona con failover manuale e un rejoin sciatto. Un primary fallisce, il team promuove una replica, il primary originale torna su, qualcuno lancia un comando di “rejoin to the cluster” senza prima controllare che il primary originale non avesse write che non si erano replicate prima del fallimento. Quelle write vengono droppate silenziosamente, o peggio, vengono fuse silenziosamente in un modo che crea duplicati.

Cluster a due nodi senza consenso. La ricetta classica: un primary e una replica con heartbeat reciproco. Il link di heartbeat fallisce, entrambi i nodi si auto-promuovono, entrambi accettano write per la durata della partition, e il rejoin fonde le storie divergenti con qualunque euristica scelga l’operatore alle 3 di notte.

In ogni caso l’errore di fondo è lo stesso: non c’era un requisito di maggioranza, non c’era un fencing token, non c’era un consensus protocol che decidesse autorevolmente “questo nodo è il leader e solo questo nodo.” Lo split brain è quello che succede quando la leadership viene decisa da qualcosa di meno rigoroso di un quorum.

Le mitigazioni

Un piccolo insieme di pratiche previene lo split brain in modo affidabile.

Usa un vero sistema di consenso per la cluster membership. Raft (etcd, Consul, la variante ZAB di ZooKeeper) è il default moderno. La membership e la leadership del cluster sono gestite da un consensus protocol, con quorum di maggioranza, con term monotonici. Non inventartelo da solo.

Gira con cluster di numero dispari di tre o più. Cinque è meglio di tre per la produzione. Due è peggio di uno. Quattro è peggio di tre.

Implementa fencing token al confine. Quando il leader scrive su uno storage esterno o su un servizio esterno, allega il token. Quando lo storage o il servizio riceve la write, controlla il token contro il più alto visto.

Usa STONITH per HA legacy. Shoot The Other Node In The Head: quando un nodo perde il suo lease, il fencing a livello hardware lo spegne forzatamente in modo che non possa accettare write durante il gap. Questa è la risposta più vecchia, brutale ed efficace per i cluster ad alta disponibilità costruiti su shared storage.

Non girare niente di importante su un cluster a due nodi. O accetti l’operatività su singolo nodo o passi a tre.

Cosa prepara questa lezione

Lo split brain è la failure mode di copertina dei sistemi distribuiti: quella con le conseguenze peggiori e quella più spesso fraintesa. Le difese (quorum, fencing, dimensioni di cluster sensate) sono tutte ben note, ma ogni anno i team rilasciano sistemi che ne sono privi e pagano il conto quando la loro rete ha una brutta giornata. La Jepsen test suite, gestita da Kyle Kingsbury (Aphyr), ha passato oltre un decennio a pubblicare report dettagliati di database distribuiti che falliscono sotto partition; molti dei fallimenti si riducono a “il sistema in realtà non aveva il quorum dove diceva di averlo.”

Questa lezione ha coperto la failure mode drammatica. La prossima lezione copre il dolore quotidiano più comune: le query cross-shard. La maggior parte dei workload non vede mai uno split brain; vede “questa query deve parlare con quattro shard e uno è lento” ogni minuto di ogni giorno. Le strategie per convivere con questa realtà ordinaria sono diverse, ed è da lì che riprende la lezione 31.

Citazioni e ulteriori letture

  • Martin Kleppmann, “Designing Data-Intensive Applications” (O’Reilly, 2017), Capitolo 9 (consistency e consenso). La trattazione lunga di un libro su split brain, fencing e quorum.
  • Kyle Kingsbury (Aphyr), “Jepsen”, https://jepsen.io/analyses (consultato 2026-05-01). Un decennio di analisi di database distribuiti che falliscono sotto partition. Prendi quasi qualunque report per un caso di studio concreto.
  • Diego Ongaro e John Ousterhout, “In Search of an Understandable Consensus Algorithm”, USENIX ATC 2014, https://raft.github.io/raft.pdf (consultato 2026-05-01). Il paper di Raft, che introduce esplicitamente i term (i fencing token del protocollo).
  • Documentazione Pacemaker, “Fencing and STONITH”, https://clusterlabs.org/pacemaker/doc/ (consultato 2026-05-01). Il riferimento HA legacy per il fencing hardware.
  • Lezione 14 di questa serie (consenso, Paxos e Raft) e lezione 16 (semantica di delivery e idempotency) per i building block che questa lezione assume.
Cerca