La lezione precedente ha lasciato CAP in una posizione un po’ poco lusinghiera. Il teorema è corretto, ha un enunciato preciso, e descrive davvero qualcosa di reale: in presenza di una network partition, devi scegliere tra consistency e availability. Ma è muto sul caso che occupa quasi tutto il tempo del tuo sistema, cioè il caso in cui non c’è alcuna partition. Il tuo database distribuito si trova nello stato di partition per qualche secondo a settimana, in una settimana storta. Il restante 99,99% del tempo, CAP non dice nulla a riguardo.
Daniel Abadi se ne è accorto nel 2010 e ci ha dato il framework che colma la lacuna. Lo ha chiamato PACELC. L’acronimo è brutto e l’idea è pulita. Lascia che lo enunci una volta e poi lo svisceri:
PACELC: se Partition, scegli Availability o Consistency; Else, scegli Latency o Consistency.
La prima metà (PAC, “se Partition, A o C”) è esattamente CAP. La seconda metà (ELC, “Else, L o C”) è la parte che CAP non ha nominato. Dice: anche quando nulla è rotto, anche su una rete perfettamente sana, anche un martedì pomeriggio senza incident in corso, stai comunque scambiando latency contro consistency su ogni write a un sistema replicato. Quel trade-off non compare in CAP perché CAP riguarda solo il caso partizionato. PACELC lo trascina alla luce.
Perché il caso quotidiano ha un proprio trade-off
Per vedere perché il trade-off ELC esiste, immagina un database fortemente consistent con replica in tre region: London, Frankfurt e Singapore. Strong consistency significa che qualsiasi read, da qualsiasi region, restituisce l’ultimo write committato. Per onorare quella promessa, il sistema deve assicurarsi che un write sia stato durabilmente confermato da un quorum di replica, comprese almeno alcune in region diverse da quella che accetta il write. Questo significa che ogni write incorre in almeno un round trip cross-region.
Un round trip cross-region è costoso. London a Frankfurt è circa quindici millisecondi. London a Singapore è più vicino a centosettanta. Anche se la tua applicazione non sta facendo nient’altro, ogni write ti costa da quindici a duecento millisecondi di wall-clock time inevitabili, pagati come tassa sulla consistency.
Ora immagina l’alternativa. Lo stesso sistema, ma l’operatore opta per write “eventually consistent”. Il write viene confermato non appena la replica locale lo ha durabilmente memorizzato. La replica verso le altre region avviene asincronamente in background. La latency scende da centosettanta millisecondi a circa due. Il costo è che, per una breve finestra, una read da Singapore potrebbe non vedere ancora il write che London ha appena confermato.
Quello è il trade-off ELC. La rete non è partizionata. Tutto è in salute. Stai comunque pagando, su ogni write, in latency oppure in consistency. Non puoi tirartene fuori. Puoi solo scegliere.
La classificazione a quattro quadranti
PACELC dà a ogni database distribuito un codice di due lettere. La prima lettera è il comportamento sotto partition: PA (available durante partition, possibilmente stale) oppure PC (consistent durante partition, possibilmente non disponibile). La seconda lettera è il comportamento senza partition: EL (low latency, possibilmente stale) oppure EC (consistent, con il costo in latency).
Combinandoli si ottengono quattro quadranti. La maggior parte dei sistemi reali si trova in uno di due di questi.
flowchart TD
A[PACELC quadrants] --> Q1[PA / EL]
A --> Q2[PC / EC]
A --> Q3[PA / EC]
A --> Q4[PC / EL]
Q1 --> Q1E[Cassandra default, DynamoDB default, Riak, S3, MongoDB default]
Q2 --> Q2E[Spanner, FaunaDB, CockroachDB, sync-replicated relational]
Q3 --> Q3E[Rare in pure form: trades availability for latency wins]
Q4 --> Q4E[Rare in pure form: trades consistency only on partition]
PA/EL: available, veloce, possibilmente stale
I sistemi PA/EL sono ottimizzati per high availability e low latency. Servono i write localmente, replicano asincronamente, e accettano che due client possano momentaneamente vedere valori diversi. Cassandra alle impostazioni di consistency di default, Riak, MongoDB con le impostazioni di default, e DynamoDB senza strongly-consistent reads sono tutti PA/EL.
Questi sistemi sono la scelta giusta per workload in cui la staleness costa poco e il downtime costa caro. Activity feed, content recommendation, session storage, ingestion analitica su larga scala, telemetria IoT. Qualsiasi caso in cui “la risposta potrebbe essere indietro di qualche secondo” non importa, ma “il sistema è non disponibile per un minuto” ti fa perdere soldi.
PC/EC: consistent, su entrambi i fronti, più lento
I sistemi PC/EC pagano il costo in latency della consistency su ogni write, e si rifiutano durante le partition per preservare la correttezza. Spanner è l’esempio da manuale. Google lo ha costruito appositamente per offrire strong consistency cross-region, usando TrueTime sincronizzato con GPS e orologi atomici per limitare il clock skew, e accettando poi che ogni write costi il round-trip time globale.
CockroachDB e FaunaDB sono discendenti più o meno open-source dell’idea Spanner. I database relazionali tradizionali con replica sincrona (Postgres con synchronous_commit = remote_apply e uno standby sincrono in un’altra zone) stanno nello stesso quadrante.
I sistemi PC/EC sono la scelta giusta per workload in cui la consistency non è negoziabile e il latency budget può assorbire decine di millisecondi per write. Ledger finanziari, sistemi di inventario in cui l’over-selling è un costo reale, dati regolatori in cui la correttezza dell’audit conta più del throughput. L’argomento di Spanner è sempre stato che, alla scala giusta, preferiresti incassare il colpo in latency piuttosto che costruirti la correttezza sopra uno store eventually-consistent da solo.
PA/EC e PC/EL: i quadranti rari
Nelle loro forme pure, gli altri due quadranti sono rari. PA/EC è un sistema che rinuncia alla consistency sotto partition ma insiste sulla consistency al di fuori. È difficile da costruire correttamente perché la finestra di partition produrrà stato divergente che le read in modalità consistent dovranno poi riconciliare. PC/EL è l’inverso: si rifiuta sotto partition, ma è eventually consistent al di fuori. Anche questo è raro perché se sei disposto a rifiutare durante una partition per la correttezza, di solito vuoi consistency anche per il resto del tempo.
I sistemi tunable sfumano i quadranti. Cassandra al consistency level QUORUM si comporta molto più vicino a PC/EC per le operation che lo usano, restando PA/EL per le operation che usano ONE. DynamoDB lascia decidere a ogni call. Quindi il diagramma a quadranti è il punto di partenza architetturale; la scelta per-operation è l’esperienza vissuta concretamente.
Un esempio concreto: un session store
Rendiamolo tangibile. Stai progettando il session store per una web application a utenti autenticati. Le session vengono lette a ogni request e aggiornate quando l’utente fa qualcosa che dovrebbe aggiornare il timestamp di ultima attività. Due domande a cui rispondere:
- Cosa succede in caso di partition? Se il session store è partizionato, rifiuti i login (PC) oppure servi una session possibilmente stale (PA)?
- Cosa succede nel caso quotidiano? Le read costano un round trip cross-region (EC), oppure leggono da una replica locale (EL)?
Per la maggior parte dei session store, la risposta è PA/EL. Una session stale va benissimo per qualche secondo (l’utente resta loggato, il timestamp di ultima attività è leggermente in ritardo rispetto alla realtà). Rifiutare tutti i login durante una partition è molto peggio che servire da una replica stale. E il latency budget per la read su ogni page load è abbastanza stretto da rendere il pagare la consistency cross-region su ogni read un cattivo trade.
Quindi l’implementazione è, diciamo, DynamoDB con eventually-consistent reads, oppure Cassandra al consistency level ONE, oppure Redis con replica asincrona. Costo più basso, latency più bassa, staleness occasionale, available durante le inevitabili piccole partition.
Ora confrontiamo con un ledger bancario. Stesse due domande:
- In caso di partition, cosa dovrebbe succedere a un prelievo? Rifiutare (PC). Meglio un errore che due prelievi dagli stessi cento dollari.
- Nel caso quotidiano, una read da un’altra region può essere stale? No (EC). Il ledger deve essere linearizable in modo che ogni account veda lo stesso saldo.
Quindi l’implementazione è Spanner, FaunaDB, o un cluster Postgres con replica sincrona attraverso due AZ. Latency più alta, costo più alto, si rifiuta durante una partition, non mente mai.
I due sistemi siedono in quadranti PACELC opposti. Sono entrambi corretti. Ciascuno è corretto per il proprio workload.
La domanda per-operation
Il modo più utile di applicare PACELC non è classificare un sistema ma classificare ogni operation. Una singola applicazione di solito ha entrambi i tipi di operation.
Un flow di login ha bisogno di PC per il check delle credenziali (meglio rifiutare che autorizzare l’utente sbagliato) e di EC per la read del ruolo e dei permessi dell’utente (è meglio che non siano stale). Costo: qualche millisecondo in più, e un errore invece di un hang durante una partition. Accettabile.
Un feed “show recent activity” sulla dashboard dello stesso utente può tranquillamente essere PA/EL. Se il feed è indietro di un secondo, non muore nessuno. Se la dashboard si rifiuta di caricare durante una piccola partition, i tuoi utenti sono più arrabbiati che se vedessero dati leggermente vecchi.
Un flow di acquisto con autorizzazione di pagamento ha bisogno di PC per l’addebito (non fare double-charge sotto partition) e di EC per la read del totale del carrello (non lasciare che l’utente paghi un importo vecchio). L’email di conferma post-acquisto può essere PA/EL.
Il punto è che “quale database dovremmo usare” è il livello sbagliato a cui porre la domanda. Il livello giusto è “di cosa ha bisogno ogni operation”, e da lì scegli un database tunable che ti permette di esprimere entrambe le modalità, oppure scegli due database (un primary a strong consistency per le operation critiche, una cache o replica eventually consistent per il resto), oppure accetti il costo in latency di far girare tutto su un sistema PC/EC perché la semplicità operativa vale più del risparmio in latency.
Una guida di lettura per i quattro quadranti
Se conosci ogni sistema solo per reputazione, la collocazione nei quadranti ti aiuta a porre domande migliori durante la valutazione. Una breve guida di lettura:
- Cassandra e Riak sono PA/EL di default. Il loro punto di forza è restare su. Tunable fino a PC/EC per singole operation usando consistency level QUORUM o ALL, ma il costo operativo cresce con la consistency.
- DynamoDB è PA/EL di default e supporta strongly-consistent reads per-call (che spostano quella call su PC/EC). Auto-scaling e fully-managed, che è il suo vero punto di forza.
- MongoDB è PA/EL di default su read da singola replica, e PC/EC se leggi dal primary con un majority write concern. I default hanno causato qualche incident pubblico genuinamente imbarazzante.
- Spanner, CockroachDB, FaunaDB sono PC/EC. La strong consistency è la feature di richiamo; la latency è il costo. Usali quando la correttezza cross-region è il requisito.
- Un Postgres o un MySQL tradizionali con replica sincrona verso un singolo standby sono PC/EC su piccola scala (un primary, una replica sincrona, entrambi nella stessa region). Aggiungi replica asincrone cross-region e ottieni PA/EL su quelle replica, il che va bene finché leggi da loro solo quando la staleness è accettabile.
I nomi cambiano posizione mentre i default cambiano tra le versioni, quindi il consiglio durevole è: chiedi “cosa fa durante una partition” e “cosa fa su una rete sana”, e scrivi le risposte. PACELC ti dà solo le quattro caselle in cui scriverle.
Cosa significa davvero “consistent”
La parola “consistent” ha fatto un duro lavoro in questa lezione e in quella precedente. CAP e PACELC la trattano entrambi come un binario: linearizable o no. Nei sistemi reali, “consistent” è uno spettro, con almeno quattro punti nominati: linearizable, sequential, causal, ed eventual. Ciascuno ha un costo diverso e un set di garanzie diverso. Un sistema che è “eventually consistent” sta facendo cose molto diverse da uno che è “causally consistent”, e la differenza conta per ciò che la tua applicazione vede.
La lezione 12 dispiega quello spettro, con esempi di quale garanzia mappa a quale tipo di bug. Alla fine, sarai in grado di leggere una pagina di documentazione di un database che dice “offriamo read-your-writes consistency dentro una session, ed eventual consistency tra session” e sapere cosa ti compra, cosa ti costa, e da quali bug applicativi ti protegge e da quali no.
Dopo di che, il resto del Modulo 2 si volge verso i pattern e i protocolli che mettono questi trade-off in pratica: strategie di replica, sistemi a quorum, risoluzione dei conflitti, e il ruolo centrale degli algoritmi di consenso in qualsiasi sistema che voglia offrire garanzie in stile CP. PACELC ti dà il menù. Il resto del modulo riguarda cosa fa effettivamente al tuo sistema ogni voce del menù.
Citazioni e letture di approfondimento
- Daniel Abadi, “Problems with CAP, and Yahoo’s little known NoSQL system” (2010), il blog post originale che ha introdotto PACELC. Riprodotto su
http://dbmsmusings.blogspot.com/2010/04/problems-with-cap-and-yahoos-little.html(consultato 2026-05-01). - Daniel Abadi, “Consistency Tradeoffs in Modern Distributed Database System Design”, IEEE Computer, 2012. Il write-up peer-reviewed di PACELC con la classificazione a quattro quadranti dei sistemi principali dell’epoca.
- James C. Corbett et al., “Spanner: Google’s Globally Distributed Database”, OSDI 2012. Il sistema PC/EC canonico, con il meccanismo TrueTime che rende trattabile la linearizability cross-region.
- Documentazione di Apache Cassandra sui consistency level tunable.
- Amazon DynamoDB Developer Guide, sezioni su consistent reads e global tables. Consultato 2026-05-01.
- La serie Jepsen di Kyle Kingsbury su
https://jepsen.io/. Test empirico di cosa fanno davvero i database sotto partition. La singola risorsa migliore per fare cross-check delle dichiarazioni di consistency dei vendor contro il comportamento osservato.