Cassandra
Le mie prime impressioni dopo aver imparato Cassandra
Venendo da SQL Server, le sorprese non riguardavano la scala. Niente join, denormalizzare di proposito, disegnare la primary key prima che la tabella esista, e un query language che sembra SQL ma rifiuta cose che SQL permette.
Ho usato Cassandra per la prima volta in un URL shortener che ho costruito per lavorare su un video di system design. Tutto quello che avevo letto su di esso prima parlava di scala e distribuzione. Le cose che mi hanno davvero sorpreso usandolo erano molto più basilari di così.
CQL sembra SQL, il che è una trappola
Il query language è abbastanza vicino a SQL che ho digitato le mie prime statement senza pensarci:
var stmt = await _session.PrepareAsync(
"SELECT code, id, long_url, short_url, created_on_utc " +
"FROM shortened_urls WHERE code = ?");
Questo è solo SQL, per quanto riesco a vedere leggendolo. La familiarità mi ha fatto presumere di sapere cosa stavo facendo, finché non ho provato qualcosa di ordinario e ha rifiutato.
Non ci sono join. Non è "i join sono lenti, evitali" come si dice dei database relazionali. Semplicemente non esistono. Lo stesso vale per le clausole WHERE arbitrarie: non puoi filtrare per qualsiasi colonna sia comoda, perché puoi fare query solo nei modi che la struttura della key della tabella supporta. Venendo da SQL Server, dove qualsiasi colonna è lecita e il query planner risolve tutto, questo sembrava meno una limitazione e più il database che era rotto.
Non è rotto. Semplicemente non ti lascia scrivere una query a cui non può rispondere efficientemente su un cluster distribuito, il che è probabilmente più onesto che lasciartela scrivere e farla venire silenziosamente pessima.
La primary key è una decisione di design, non una colonna ID
In SQL aggiungo una colonna Id soprattutto per abitudine e penso alle query dopo. In Cassandra la key determina su quale node vive il dato, e quindi cosa puoi cercare efficientemente.
La mia tabella è finita più semplice possibile:
CREATE TABLE IF NOT EXISTS url_shortener.shortened_urls
(code text PRIMARY KEY, id uuid, long_url text, short_url text, created_on_utc timestamp)
Lo short code è la primary key, non lo uuid. È l'opposto di quello che avrei fatto istintivamente in SQL, dove il GUID sarebbe stato la key e code avrebbe ricevuto un indice. Ma ogni lettura in questa applicazione è "dammi la riga per questo code", quindi il code deve essere la key. Lo uuid è solo un dato che capita di essere lì.
Prendere quella decisione giusta qui è stato facile perché il pattern di accesso è banale. Su qualcosa con più di una forma di query, avrei dovuto pensarci molto più a fondo in anticipo, e sospetto che sia lì che Cassandra diventa davvero difficile.
Denormalizzi di proposito
Il consiglio in cui continuavo a imbattermi è che se ti serve lo stesso dato disponibile tramite due key diverse, lo salvi due volte, in due tabelle, ciascuna con la key nel modo di cui quella query ha bisogno.
Ogni istinto che mi sono costruito dice che duplicare i dati è come le cose vanno storte, perché ora ci sono due copie che possono essere in disaccordo. In Cassandra quello è il design atteso, e tenere le copie sincronizzate è compito dell'applicazione piuttosto che del database. Capisco perché, dato che non ci sono join per riassemblare le cose tra i node, ma è sembrato comunque sbagliato da digitare.
Il setup coinvolge i keyspace, e la replication è un numero che scegli
Prima di creare una tabella, c'è un keyspace, che è grosso modo un database, e vuole sapere quante copie del dato tenere:
session.Execute(
"CREATE KEYSPACE IF NOT EXISTS url_shortener " +
"WITH replication = {'class':'SimpleStrategy','replication_factor':1}");
Un replication factor di uno significa una singola copia, il che va bene sul mio portatile e sarebbe una cattiva idea in produzione, dove vorresti il dato su più node così che perderne uno non perda il dato.
Ciò che mi ha colpito è che questa è una decisione che ho dovuto prendere nella prima riga del setup. In SQL Server, quante copie dei miei dati esistono è una questione di infrastruttura che non tocco mai nel codice dell'applicazione. Qui fa parte dello schema.
Era la scelta giusta per questo progetto?
Per imparare, sì. Per il progetto sui suoi meriti, onestamente no. Un URL shortener con il mio traffico sarebbe servito perfettamente da SQLite. Cassandra è costruito per una scala che non ho, e l'intero motivo per cui l'ho usato era lavorare con il database che si adatta alla versione reale del problema invece della versione che avevo davvero.
Cosa ne ho tratto
Il cambiamento utile non è stato imparare Cassandra in sé. È stato rendermi conto che in un database relazionale progetto il dato e capisco le query dopo, e qui progetto le query e il layout del dato segue di conseguenza. È un modo genuinamente diverso di iniziare, e spiega la maggior parte delle restrizioni che all'inizio sembravano arbitrarie.