Database Non Relazionali NoSQL: Modelli, Teoremi e Scelte Architetturali
Da ACID a BASE, dal singolo server al cluster planetario: quando lasciare il relazionale e perché
- Comprendere i limiti del modello relazionale nel contesto Big Data e i motivi dell'emergere dei database NoSQL
- Distinguere le proprietà ACID (RDBMS) dalle proprietà BASE (NoSQL) e capirne il trade-off architetturale
- Riconoscere le quattro categorie NoSQL (Document, Key-Value, Column-family, Graph) con esempi reali e casi d'uso
- Scegliere consapevolmente tra SQL e NoSQL in base ai requisiti di consistenza, scalabilità e pattern di accesso
1. Perché il modello relazionale non basta più?
I database relazionali sono nati negli anni '70 per gestire dati strutturati: banche, ERP, prenotazioni aeree. Hanno dominato per 40 anni. Poi è arrivato il Web 2.0, Instagram con miliardi di foto, sensori IoT che sparano 10.000 misure al secondo, log di click infiniti. E lì il modello relazionale ha iniziato a scricchiolare.
Io lo spiego sempre così: in un RDBMS tutto deve stare in tabelle rettangolari, con schema rigido. Se domani un ordine e-commerce deve contenere un array di coupon, un oggetto regalo con messaggio e tre timestamp diversi, con SQL devi fare ALTER TABLE e aggiungere tabelle ponte. Con un documento JSON, aggiungi un campo.
NoSQL = Not Only SQL. Non significa "contro SQL". Significa: esistono scenari dove il modello tabellare non è ottimale. Il termine esplode nel 2009 in un meetup a San Francisco organizzato da Johan Oskarsson per discutere di DB distribuiti open source.
I quattro limiti strutturali che ho visto io in produzione:
- Schema rigido: ALTER TABLE su una tabella da 2 miliardi di righe in MySQL può bloccare il DB per ore. In NoSQL lo schema è flessibile.
- JOIN costosi: su dataset enormi, un JOIN tra 3 tabelle da 100M di righe richiede shuffle e I/O ingestibili. NoSQL denormalizza per evitare il JOIN.
- Scalabilità verticale vs orizzontale: un RDBMS scala facendo più potente un singolo server (scale-up) ma arrivi a un tetto fisico ed economico. NoSQL è nato per lo scale-out: aggiungi nodi commodity.
- Impedance mismatch: nel codice avete oggetti con array annidati. In SQL dovete appiattirli. Con Document Store, salvate l'oggetto quasi identico a come lo avete in memoria.
Io ho pensato di partire dai limiti del relazionale perché altrimenti sembra che NoSQL sia "figo" per moda. Io lo farei così: prima vi faccio soffrire con un esercizio di ALTER TABLE su 50 milioni di righe simulate, poi vi mostro come MongoDB aggiunge un campo senza battere ciglio. Così capite il perché, non solo il come.
2. ACID vs BASE: due filosofie a confronto
I RDBMS garantiscono ACID. I NoSQL sacrificano un po' di consistenza immediata per ottenere disponibilità e velocità, e adottano BASE. Non è meglio o peggio: è un trade-off.
| Proprietà | ACID — RDBMS tradizionale | BASE — NoSQL distribuito |
|---|---|---|
| A / BA | Atomicità: tutto o niente, rollback completo se una parte fallisce | Basically Available: il sistema risponde SEMPRE, anche con dati non aggiornati |
| C / S | Consistenza: da stato valido a stato valido, vincoli mai violati | Soft State: lo stato può cambiare anche senza input, per propagazione replica |
| I / E | Isolamento: transazioni concorrenti come se fossero seriali | Eventually Consistent: col tempo tutti i nodi convergono allo stesso valore |
| D | Durabilità: dopo il commit, sopravvive anche al crash (WAL) | Durabilità tunable: W=1 veloce ma rischioso, W=majority più sicuro |
Aggiorni lo stato su Facebook. Un amico in Australia per 2-3 secondi vede ancora il vecchio stato. È acceptable. Quello è eventually consistent. Se invece fai un bonifico da 10.000€, non è acceptable che per 3 secondi il saldo sia sballato. Lì serve ACID puro. Io ho pensato di farvi scegliere voi: per quale feature accettereste BASE e per quale no?
Pensare che BASE significhi "perde dati". No. Significa che per un breve intervallo (millisecondi/secondi) nodi diversi possono vedere valori diversi. Alla fine convergono. L'errore all'esame è dire "NoSQL non è affidabile". Io lo correggerei così: NoSQL è affidabile, ma con garanzie diverse. Se ti serve consistenza immediata, usi write concern majority o quorum.
3. Il teorema CAP di Brewer
Un sistema distribuito può garantire al massimo 2 su 3 tra: Consistency (tutti i nodi vedono gli stessi dati nello stesso momento), Availability (ogni richiesta riceve una risposta non errore), Partition Tolerance (sistema funzionante anche se la rete si spezza).
Poiché le partizioni di rete in un sistema reale sono inevitabili, la scelta è CP vs AP. I RDBMS tradizionali monolitici sono CA: funzionano bene fino a quando c'è una rete perfetta. Ma su cluster planetario, CA non esiste.
| Classe | Sistema | Scelta pratica |
|---|---|---|
| CA — Consistenza + Disponibilità | MySQL / PostgreSQL singolo nodo | Ottimo fino a ~10TB, poi scale-up costoso |
| CP — Consistenza + Partizionamento | MongoDB (majority write), HBase, Zookeeper | Se la rete si parte, alcune letture possono fallire per mantenere consistenza |
| AP — Disponibilità + Partizionamento | Cassandra, CouchDB, DynamoDB, Redis Cluster AP | Sempre disponibile, ma letture possono dare valori vecchi |
Io ho pensato di farvi disegnare il triangolo CAP alla lavagna. Io lo farei così: prendete un e-commerce. Durante il Black Friday la rete tra datacenter EU e US si spezza. Vuoi che un utente possa ancora aggiungere al carrello (AP) anche se il magazzino potrebbe essere oversellato per 5 sec, o vuoi bloccare tutto (CP) per non vendere ciò che non hai? Non c'è risposta giusta universale, c'è contesto.
4. Le 4 categorie NoSQL: panoramica
Non esiste "un NoSQL". Esistono quattro famiglie completamente diverse. Scegliere la famiglia sbagliata è peggio che restare su SQL.
| Modello | Esempi | Punto di forza | Quando evitarlo |
|---|---|---|---|
| Document Store | MongoDB, CouchDB, Firestore | Schema flessibile, embedding, query JSON | Transazioni complesse multi-document |
| Key-Value | Redis, DynamoDB, Memcached | O(1) in RAM, TTL, contatori | Query su campi del valore |
| Column-Family | Cassandra, HBase, ScyllaDB | Scritture massive, petabyte, time-series | Ricerca ad-hoc, JOIN |
| Graph | Neo4j, Neptune, ArangoDB | Traverse relazioni profonde veloci | Aggregazioni tabellari massive |
5. Document Store — MongoDB e JSON annidato
Ogni record è un documento BSON (JSON binario) con struttura libera. I documenti stanno in collezioni. Ogni documento può avere campi diversi. È il modello più vicino a come programmate in JavaScript/PHP con array associativi.
E-commerce prodotto con attributi variabili: una scarpa ha taglia/colore, un laptop ha RAM/CPU/GPU, un libro ha ISBN/autore. Con SQL fareste EAV (entity-attribute-value) infernale. Con MongoDB ogni prodotto ha campi diversi, naturale. Io lo farei così nel vostro progetto di 4a: catalogo in MongoDB, ordini transazionali in PostgreSQL.
Embedding vs Referencing — scelta critica
| Strategia | Come funziona | Pro | Contro |
|---|---|---|---|
| Embedding | Dati correlati dentro lo stesso documento | Lettura O(1), niente JOIN, atomico | Duplicazione, documento >16MB problematico |
| Referencing | ID di altro documento + $lookup | Niente duplicati, normalizzazione | Due query, simile a JOIN, più lento |
Vedo spesso studenti che embeddano tutto. Se un utente ha 10.000 ordini, il documento esplode e superare i 16MB di limite BSON blocca tutto. Io ho pensato di darvi questa regola pratica che uso io: embedding se rapporto 1-pochi e dati letti sempre insieme; referencing se 1-molti o molti-molti. Ordini di un cliente con < 100? Embed. Post di un utente in un social? Reference.
6. Key-Value Store — Redis e l'accesso O(1)
Il modello più semplice che esista: chiave → valore. Redis tiene tutto in RAM, quindi legge e scrive in microsecondi. Il valore può essere stringa, lista, set, hash, sorted set.
Io ho pensato di farvi iniziare da Redis perché è il più gratificante: installi, lanci due comandi e vedi O(1). Io lo farei così nei vostri progetti: MySQL per i dati veri, Redis come cache davanti. Prima query costosa su MySQL → salvo risultato in Redis con SET ... EX 300 → le prossime 299 letture sono in RAM. Risparmiate il 90% di CPU sul DB. È il pattern che vedrete in ogni azienda.
Casi d'uso che non si possono fare efficientemente in SQL:
- Cache applicativa: query SQL da 200ms diventa risposta Redis da 0.5ms
- Sessioni: EX 3600 fa scadere automaticamente la sessione, niente cron
- Pub/Sub real-time: notifiche tra microservizi senza Kafka per casi semplici
- Rate limiting: INCR + EXPIRE per limitare 100 richieste/min per IP
7. Column-Family — Cassandra e la scrittura massiva
I dati sono organizzati per colonne, non per righe. Ogni riga ha una chiave e famiglie di colonne con numero arbitrario di colonne. Nasce per scrivere moltissimo e leggere poche colonne su petabyte. Architettura masterless: tutti i nodi sono uguali, niente single point of failure. Scrittura = commit log + memtable → flush su SSTable immutabile.
In SQL leggi una riga intera anche se ti serve una colonna. In Column-Family leggi solo le colonne che ti servono. Se hai tabella da 500 colonne IoT (temperatura, umidità, pressione...) e vuoi solo temperatura dell'ultimo mese, Cassandra legge solo quella famiglia di colonne.
Timeline di Twitter/X: ogni tweet va scritto una volta ma letto milioni di volte da follower diversi. Con RDBMS fareste JOIN timeline enorme. Con Cassandra denormalizzate: scrivete il tweet in una tabella per ogni follower (scrittura amplificata ma lettura O(1)). Io lo farei così per farvi capire: 300M di utenti × scritture veloci ok, lettura timeline in <10ms.
CQL assomiglia a SQL ma non lo è. In Cassandra non puoi fare WHERE su colonna non parte della PRIMARY KEY senza ALLOW FILTERING che è un killer. Molti fanno SELECT * WHERE temperatura > 30 senza indice → full scan cluster → timeout. Io ho pensato di farvi sbattere la testa su questo errore una volta sola in lab, così non lo dimenticate più.
8. Graph Database — Neo4j e le relazioni come cittadini di prima classe
Nei graph DB, nodi (entità) e archi (relazioni) hanno proprietà proprie e le relazioni sono fisicamente puntatori memorizzati, non calcolate a runtime con JOIN. Su traverse profonde (amici degli amici di amici) è 100-1000× più veloce di SQL.
Con SQL equivalente servirebbero 3 JOIN ricorsivi con CTE ricorsiva, esponenzialmente più lenti perché ogni JOIN deve scansionare indici.
Io ho pensato di mettere i Graph per ultimi perché sono i meno intuitivi ma i più potenti per certi problemi. Io lo farei così: vi chiedo di modellare su carta una rete di 1M utenti con relazioni CONOSCE. Con SQL avete tabella Persona + tabella Amicizia. Con Neo4j avete nodi + archi fisici. Quando dovete trovare tutti i percorsi di 4 livelli tra due persone, Neo4j è nativo, SQL muore.
9. Quando scegliere SQL vs NoSQL? Tabella decisionale
La domanda non è "qual è meglio?" ma "qual è giusto per questo requisito?". Io ho pensato di darvi questa tabella che uso io in consulenza per decidere al volo.
| Criterio | Scegli SQL | Scegli NoSQL |
|---|---|---|
| Schema dei dati | Fisso, relazioni chiare, normalizzato | Flessibile, variabile per record, semi-strutturato |
| Consistenza | Critica (banca, prenotazioni, fatture) | Eventual consistency ok (like, feed, cache) |
| Scalabilità | Verticale, fino a 10TB, scale-up | Orizzontale, da 10TB a PB, scale-out + sharding |
| Query | JOIN multipli, aggregazioni GROUP BY complesse | Accesso per chiave, range scan, traverse grafo |
| Volume | KB → decine di GB, migliaia di scritture/sec | GB → petabyte, milioni di scritture/sec |
| Velocità scrittura | Moderata (overhead lock ACID) | Molto alta (no lock globale, SSTable append) |
| Esempi | ERP, gestionale, banca, e-commerce transazionale | Social, IoT, gaming, real-time analytics |
Vi do tre app: A) app per studio medico con fatture e ricette, B) app per monopattini con 50k mezzi che inviano GPS ogni 3 sec, C) LinkedIn clone. Io lo farei così: A = PostgreSQL rigoroso ACID, B = Cassandra + Redis, C = Neo4j per grafo connessioni + MongoDB per profili + Redis per feed. Se scegliete tutto con un solo DB, state forzando.
10. Polyglot Persistence: il mondo reale
In produzione nessuna grande app usa un solo database. Si usa Polyglot Persistence: il DB giusto per ogni tipo di dato nella stessa architettura.
Io ho pensato di chiudere con questo perché è quello che mi ha insegnato il campo: non innamoratevi di un DB. Io lo farei così: partite sempre da PostgreSQL, che con JSONB ormai fa anche il document-like. Poi aggiungete Redis il primo giorno che sentite lentezza. Poi, solo se avete davvero scale-out o grafi profondi, aggiungete Cassandra o Neo4j. La regola che uso io: aggiungi complessità solo quando il dolore è misurabile, non per hype.
📌 Riepilogo — Punti chiave
- Limiti relazionale: schema rigido costoso su Big Data, JOIN inefficienti su miliardi di righe, scale-up con tetto fisico, impedance mismatch tra oggetti e tabelle
- ACID vs BASE: ACID = Atomicità, Consistenza, Isolamento, Durabilità (correttezza assoluta). BASE = Basically Available, Soft State, Eventually Consistent (disponibilità + scalabilità, consistenza finale)
- CAP: in un sistema distribuito puoi garantire al massimo 2 tra Consistency, Availability, Partition Tolerance → nella pratica CP (MongoDB majority) vs AP (Cassandra, DynamoDB)
- Document Store (MongoDB): JSON/BSON annidato, embedding vs referencing, ideale per cataloghi e profili con schema flessibile, query per campo con indice
- Key-Value (Redis): O(1) in RAM, TTL automatico, contatori con INCR, liste per job queue, sorted set per leaderboard — la cache universale
- Column-Family (Cassandra): scritture massive append-only su SSTable, masterless, letture per famiglia di colonne, TTL nativo, time-series IoT e timeline social
- Graph (Neo4j): nodi + archi con proprietà, relazioni come puntatori fisici, Cypher per traverse amici-degli-amici e fraud detection ad anello, 100× più veloce di JOIN ricorsivi su grafi profondi
- Scelta: SQL quando consistenza immediata e JOIN complessi; NoSQL quando scala orizzontale, schema variabile, velocità di scrittura estrema o relazioni profonde
- Polyglot persistence: architettura reale = PostgreSQL transazionale + Redis cache + MongoDB catalogo + Elasticsearch ricerca + eventuale Cassandra/Neo4j se serve scale o grafo
📚 Fine Modulo
A questo punto hai imparato come implementare database relazionali e NoSQL in contesti diversi. Ora sei in grado di scegliere il modello più adatto alle tue esigenze di performance, scalabilità e consistenza.
