Le otto fallacie ti hanno spiegato perché i sistemi distribuiti sono difficili. Il teorema CAP ti racconta lo specifico, inevitabile trade-off che cade fuori da una di quelle fallacie (la rete è affidabile, fallacia 1) quando la prendi sul serio. CAP è il risultato più citato e più frainteso nei sistemi distribuiti. Sta su ogni lista di domande da colloquio sull’architettura, su ogni pagina di marketing dei vendor di database, e dietro a ogni “siamo eventually consistent” detto con la mano sospesa in una riunione di design.
Questa lezione fa due cose. Primo, enuncia cosa dice davvero il teorema CAP, che è un’affermazione più stretta e più attenta dell’interpretazione abituale. Secondo, attraversa le scelte reali che fanno i sistemi reali, con esempi, così la prossima volta che qualcuno ti dirà che il suo database è “AP” saprai quale sia la domanda successiva da fare.
Cosa dice davvero CAP
Eric Brewer ha tenuto una keynote alla conferenza PODC nel 2000 in cui ha congetturato che un sistema distribuito a dati condivisi potesse fornire al massimo due garanzie su tre: consistency, availability e partition tolerance. Seth Gilbert e Nancy Lynch hanno trasformato quella congettura in un teorema formale nel 2002.
Le tre garanzie, nel senso preciso in cui le usa il teorema:
- Consistency (nello specifico, linearizability): ogni read vede la write più recente, come se il sistema avesse una singola copia aggiornata.
- Availability: ogni richiesta a un nodo non in failure restituisce una risposta in tempo finito. Non necessariamente una risposta veloce, ma una risposta che non sia “rifiuto.”
- Partition tolerance: il sistema continua a funzionare anche quando messaggi arbitrari tra nodi sono persi o ritardati indefinitamente.
Il teorema dice che non puoi avere tutte e tre simultaneamente. L’interpretazione poco utile è “scegline due su tre”, che ha causato più confusione di qualunque altra frase nel campo.
La ragione per cui “scegline due” è sbagliato è che la partition tolerance non è davvero una scelta in nessun sistema distribuito non banale. Se il tuo sistema gira su più di una macchina connessa da una rete, le partition possono accadere. Le fallacie te lo garantiscono. Non puoi rinunciare alla partition tolerance decidendo che preferiresti avere CA. Puoi scegliere cosa fa il tuo sistema quando, inevitabilmente, accade una partition.
Quindi il modo preciso di leggere CAP è questo: in presenza di una partition di rete, devi scegliere tra consistency e availability per ogni operation. Fuori da una partition, quel trade-off non si applica, e puoi avere entrambe. CAP è silenzioso sul caso quotidiano. Lo sistemeremo con PACELC nella prossima lezione.
Questa lettura precisa ha due conseguenze importanti. Primo, rende “AP system” solo mezza frase. AP per quali operation? Sotto quali condizioni? Molti sistemi sono AP per alcune operation e CP per altre, e l’operatore sceglie per ogni chiamata. Secondo, rende la domanda di cosa fa il tuo sistema durante una partition molto più concreta della versione da manuale. Il lavoro di design interessante è nel comportamento durante la partition.
Le tre categorie, in pratica
I sistemi reali si trovano in una di tre posizioni.
CP: rifiutare invece di servire dati stale
Un sistema CP, quando rileva una partition, preferisce fallire chiuso. Un nodo che non riesce a raggiungere un quorum dei suoi peer rifiuterà nuove write (e possibilmente nuove read) invece di servire un valore di cui non può provare l’attualità. L’utente riceve un errore. I dati restano corretti.
I sistemi CP archetipici sono i servizi di coordinamento: ZooKeeper, etcd, e Consul (nella sua modalità strongly-consistent). Sono i sistemi che usiamo per gestire la leader election, memorizzare la configuration, e fungere da source of truth per “chi possiede questo lock” in un sistema distribuito più grande. Sono CP perché, se li stai usando per coordinarti, la cosa peggiore che possono fare è consegnare due lock a due nodi che pensano entrambi di averlo. Nel caso di partition, rifiutare il lock è corretto; far finta di concederlo due volte è catastrofico.
L’archetipo del ledger bancario segue la stessa logica. Se due region del ledger di una banca sono in disaccordo sul fatto che il conto abbia 100 euro, e la partition tra di loro impedisce la riconciliazione, il comportamento corretto della banca è rifiutare il prelievo invece di rischiare che entrambi i lati lo autorizzino. Meglio un cliente arrabbiato che due prelievi sugli stessi cento euro.
I sistemi CP barattano l’availability durante una partition con la garanzia che non leggerai mai dati stale e non vedrai mai una write che si perde silenziosamente in seguito. Per i dati in cui la correttezza è prioritaria e l’availability può essere sacrificata temporaneamente, è la chiamata giusta.
AP: servi quello che hai
Un sistema AP, durante una partition, preferisce fallire aperto. Ogni nodo continua a rispondere alle richieste usando i dati più recenti che ha. Quando la partition si risana, le versioni divergenti devono essere riconciliate, sia automaticamente (last-write-wins, vector clocks, CRDT) sia tramite logica di livello applicativo.
DNS è il sistema AP più familiare al mondo. I resolver DNS nel tuo laptop, nel tuo ISP, e nelle varie cache tra te e il server autoritativo possono tutti contenere versioni leggermente diverse di un record. Non c’è una singola copia aggiornata. Il sistema è “eventually consistent” nel senso che, dato abbastanza tempo e abbastanza scadenze di TTL, le cache convergono sull’ultimo valore. Ma in qualunque momento, due client possono risolvere lo stesso nome verso due indirizzi diversi. Il sistema resta in piedi. Il costo è che a volte ti capita la risposta di ieri.
Cassandra, nella sua modalità tunable di default, è un database AP. Accetta write su qualsiasi replica, le propaga in background, e usa last-write-wins (con timestamp) per risolvere i conflitti quando due write per la stessa chiave arrivano su lati diversi di una partition. S3 è stato eventually consistent in modo famoso per molti anni e ha aggiunto la strong read-after-write consistency solo nel 2020.
AP è la chiamata giusta quando la stale-ness è tollerabile e l’unavailability no. Un carrello della spesa è l’esempio canonico: se due dispositivi aggiungono la stessa SKU al carrello durante una partition, preferisci unire i carrelli dopo piuttosto che rifiutare l’operation ora. Caso peggiore, l’utente rimuove il duplicato. Caso migliore, non se ne accorge mai.
Tunable: scegli per operation
I sistemi più utili in produzione sono quelli che ti lasciano scegliere, per ogni operation, quanto debba essere forte la consistency. Cassandra è l’esempio da manuale. Ogni read e ogni write specifica un consistency level: ONE, QUORUM, ALL, LOCAL_QUORUM, e così via. Una read a QUORUM combinata con una write a QUORUM ti dà comportamento linearizable a patto che si possa raggiungere una maggioranza delle replica. Una read a consistency level ONE restituisce la prima risposta che qualunque replica ti dà, che potrebbe essere stale, ma è veloce e resta available anche quando la maggior parte del cluster è partizionata via.
DynamoDB ha una versione più semplice della stessa idea: ogni read può essere strongly consistent (che costa di più e rifiuta di servire durante una partition che riguarda la partition key rilevante) oppure eventually consistent (più economica, più veloce, può restituire dati stale).
I sistemi tunable sono il modo in cui le applicazioni reali ottengono il meglio di entrambi i mondi. Il flusso di login è una read a QUORUM: credenziali stale sarebbero un bug vero. Il feed “attività recente” è una read a ONE: un feed stale di trenta secondi va benissimo.
Da dove vengono davvero le partition
Il teorema CAP a volte viene accusato di essere accademico con la motivazione che “le reti reali raramente si partizionano”. Non è vero, soprattutto in ambienti cloud. Le cause reali del comportamento di partition, su un sistema di produzione vero nel 2026, includono:
- Un’intera availability zone che perde connettività per decine di secondi durante un cambio di routing.
- Un security group mal configurato che blocca traffico intra-cluster su una particolare porta per un singolo rolling deploy.
- Un nodo coordinator sovraccarico che droppa gli heartbeat e viene erroneamente marcato come partizionato dal resto del cluster.
- Un link cross-region che si degrada brevemente durante una riparazione di fibra.
- Un bug in una service mesh che causa il fallimento degli handshake mutual TLS per una combinazione di versioni.
La lezione è che le partition in pratica sono raramente lo scenario drammatico del “data centre che cade nell’oceano”. Sono piccoli blip di dieci secondi che accadono abbastanza spesso da far sì che qualunque cluster non banale ne veda uno la maggior parte delle settimane. CAP è il framework che ti dice cosa il tuo sistema dovrebbe fare in quei dieci secondi.
Un flowchart decisionale
La domanda chiave, su ogni operation, è cosa fare durante una partition: rifiutare, o servire dati possibly-stale.
flowchart TD
A[Client makes a request] --> B{Network partition between this node and the rest?}
B -- No --> C[Serve normally: consistent and available]
B -- Yes --> D{What did the operator choose for this operation?}
D -- CP path --> E[Refuse the request, return error]
D -- AP path --> F[Serve from local replica, may be stale]
F --> G[Reconcile after the partition heals]
E --> H[Caller decides whether to retry or fail]
Il ramo CP è ciò che fanno ZooKeeper, etcd, e una read strongly-consistent di Spanner. Il ramo AP è ciò che fanno DNS, una read di default in Cassandra, e una read eventually-consistent in DynamoDB. I sistemi reali hanno entrambi i rami disponibili e lasciano scegliere all’applicazione.
Letture sbagliate comuni
Alcune affermazioni su CAP che vedrai in giro e che vale la pena segnalare:
- “Il nostro database è CA.” Non esiste in un sistema distribuito. I database single-machine (un Postgres non replicato) sono tecnicamente CA nel senso banale che non ci sono partition da tollerare, ma appena li replichi su più macchine devi scegliere un comportamento di partition.
- “Siamo AP, quindi siamo sempre available.” Non è vero. Sei available durante una partition. Fuori da una, sei soggetto agli stessi failure di chiunque altro. “Available” in CAP ha anche un significato preciso: ogni nodo non in failure risponde. Un sistema può essere AP ed essere comunque down perché tutti i suoi nodi sono falliti.
- “L’eventual consistency è abbastanza buona.” A volte lo è, a volte non lo è. La domanda è cosa fa l’applicazione quando osserva dati stale. Se la risposta è “l’utente non se ne accorge e il sistema riconcilia”, bene. Se la risposta è “addebitiamo due volte un cliente”, non bene.
- “Abbiamo scelto AP per la performance.” Questo confonde due trade-off diversi. AP contro CP è sul comportamento di partition. Latency contro consistency è il trade-off quotidiano, ed è il soggetto di PACELC. La maggior parte delle volte, quando qualcuno dice di aver scelto AP per la performance, intende di aver scelto EL (eventual consistency fuori dalle partition per bassa latency) e non ha ancora pensato attentamente a cosa accade durante una partition.
L’ultimo punto è ciò che motiva la prossima lezione.
Cosa CAP si perde
CAP è una risposta utile al 90% e fuorviante al 100%. Il suo punto cieco è che descrive solo il sistema durante una partition, e il tuo sistema passa quasi tutto il suo tempo non in una partition. Durante il caso quotidiano, stai comunque barattando qualcosa. Il teorema CAP semplicemente non lo dice.
Nel 2010, Daniel Abadi ha sottolineato questo in un paper e in un blog post, e ha proposto un’estensione chiamata PACELC. L’acronimo è scomodo e l’idea è semplice: “se Partition, scegli Availability o Consistency; Else, scegli Latency o Consistency.” La prima metà è CAP. La seconda metà è la parte che CAP ha dimenticato.
PACELC trasforma lo spazio di design da una decisione a un asse a una a due assi, e il secondo asse è quello che davvero conta la maggior parte dei giorni, perché la maggior parte dei giorni non c’è una partition. Ci passeremo sopra la prossima lezione, inclusa una classificazione a quattro quadranti dei principali database distribuiti (Cassandra, DynamoDB, Spanner, Riak, FaunaDB) così che tu possa vedere a colpo d’occhio quali trade-off sta facendo ciascuno.
Per ora, ciò che ti porti via da questa lezione è che il teorema CAP è una cornice utile per una specifica domanda (cosa fa il tuo sistema durante una network partition) e che la risposta è per-operation, non per-system. Ogni volta che senti “siamo un AP system”, la tua domanda di follow-up dovrebbe essere “per quali operation, con quale strategia di riconciliazione, e cosa fa il resto del sistema?” Se la risposta è vaga, hai appena identificato una decisione architetturale che non è ancora stata presa.
Riferimenti e letture aggiuntive
- Eric Brewer, “Towards Robust Distributed Systems”, PODC keynote, 2000. Slide e discussione archiviate su
https://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf(consultato 2026-05-01). - Seth Gilbert e Nancy Lynch, “Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services”, ACM SIGACT News, 2002. La dimostrazione formale.
- Eric Brewer, “CAP Twelve Years Later: How the Rules Have Changed”, IEEE Computer, 2012. La retrospettiva di Brewer su cosa il teorema era e non era.
- Martin Kleppmann, “A Critique of the CAP Theorem” (2015), disponibile su
https://arxiv.org/abs/1509.05393. La critica tecnica più approfondita dell’interpretazione standard. - Per il comportamento operativo dei sistemi CP: la documentazione di etcd su quorum e split-brain, e la descrizione del protocollo “ZAB” di ZooKeeper.
- Per la tunable consistency: la documentazione di Apache Cassandra sui consistency level, e la sezione del developer guide di DynamoDB sulla read consistency. Entrambe consultate 2026-05-01.