← tutti gli articoli

Databases

SQL o NoSQL? Come penso a questa scelta

La domanda non è quale database sia migliore. È se conosco i miei access pattern in anticipo, e se il dato ha relazioni che ho bisogno che il database stesso protegga.

SQL o NoSQL? Come penso a questa scelta

Per molto tempo ho pensato che fosse una domanda sulla scala: SQL per le app normali, NoSQL una volta che diventi grande. Non è proprio così. I due progetti in cui ho davvero fatto questa scelta deliberatamente mi hanno insegnato che riguarda qualcos'altro del tutto.

La domanda che faccio davvero per prima

So come verrà letto questo dato prima di progettarlo?

Sembra al contrario, dato che in SQL progetti lo schema attorno al dato e poi ci fai query in qualsiasi modo ti serva più avanti. Quella flessibilità è tutto il senso di un database relazionale, ed è per questo che è il default giusto quando ancora non so ogni modo in cui il dato verrà usato.

NoSQL sostanzialmente inverte questo. Progetti attorno alle query che eseguirai, e la forma dello storage segue dal pattern di accesso.

Dove questo ha preso la decisione al posto mio

L'URL shortener che ho costruito è l'esempio più chiaro. Fa solo una cosa: dato uno short code, restituisce l'URL originale. Un lookup, con una key, sempre. Niente join, niente query di reporting, niente "mostrami tutti gli URL creati martedì scorso raggruppati per dominio."

Quello è un pattern di accesso che conoscevo completamente prima di scrivere una riga di codice, che è esattamente il caso in cui Cassandra ha senso. È costruito per lookup con key e distribuisce i dati tra i node man mano che cresce.

Il payment orchestrator è andato nella direzione opposta, su SQL, e il motivo non era la scala nemmeno lì. Un pagamento ha attempt, e fraud flag, e quelle relazioni devono essere imposte. Un attempt che referenzia un pagamento che non esiste è dato corrotto, e voglio che il database lo rifiuti invece di fidarmi che il mio codice non commetta mai quell'errore.

Le relazioni e le transaction sono la vera linea di demarcazione

Quell'esempio di pagamento è la versione generale. Quando la correttezza dei miei dati dipende dal fatto che diverse righe cambino insieme o per niente, un database relazionale mi dà quella garanzia direttamente.

Il caso del trasferimento di denaro è quello ovvio, dove addebitare un account e accreditarne un altro devono riuscire entrambi o fallire entrambi. Ma è più comune di così. Creare un ordine e le sue voci, aggiornare un pagamento e registrare l'attempt che l'ha cambiato. Ovunque "metà di questo è successo" sia uno stato inaccettabile, le transaction stanno facendo un lavoro vero per me.

Le foreign key sono la stessa idea applicata alle relazioni. Il database rifiuta di lasciarmi creare un orfano, così quell'intera classe di bug non arriva mai in produzione a prescindere da cosa fa il mio codice applicativo.

Dove ricorrerei davvero a NoSQL

Alcuni casi in cui penso sia chiaramente giusto, oltre a quello del lookup con key:

Dati senza forma fissa, dove record diversi hanno legittimamente campi diversi e forzarli in colonne significa o una tabella molto larga piena di null o un mucchio di tabelle quasi vuote unite con join.

Volume di scrittura molto alto dove le scritture sono indipendenti tra loro, tipo event log o telemetria, dove niente deve essere coerente con nient'altro nel momento in cui viene scritto.

Caching, che in realtà è una cosa separata ma sta nella stessa famiglia. Redis davanti all'URL shortener non è una scelta di database, è uno strato che tiene dati caldi che anche il database vero possiede.

Cosa direi a me stesso di un anno fa

Vai di default su SQL. Non perché è migliore, ma perché è quello che non richiede di avere ragione sui tuoi access pattern in anticipo, e come sviluppatore junior su un progetto nuovo di solito non ce l'ho.

Ricorri a NoSQL quando riesci a nominare il motivo specifico. "Scala meglio" non è un motivo, dato che un database relazionale su un server normale gestisce molto più traffico di quanto la maggior parte dei progetti vedrà mai. "Questo è un lookup con una key e mi serve distribuito tra i node" è un motivo. "Questi record genuinamente non condividono una forma" è un motivo.

L'errore che avrei fatto senza costruirli entrambi è presumere che la scelta riguardi la dimensione. Riguarda se conosco gli access pattern in anticipo, e se il dato ha relazioni che voglio che il database stesso protegga.