Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Introduzione a Apache Cassandra

Installare e comprendere la logica del server "noSQL" di casa Apache
Installare e comprendere la logica del server "noSQL" di casa Apache
Link copiato negli appunti

Con l'avvento del Web 2.0 la rete ha sperimentato per la prima volta le criticità legate alla gestione infrastrutturale di un ambiente che dovesse sostenere ed accentrare le attività tipiche di una community. Indirizzare correttamente tematiche come lo scaling, il tempo di risposta e la gestione di grossissimi volumi di dati è lentamente diventato essenziale per una cerchia sempre più vasta di player web, tra i quali cito Facebook, YouTube, Flickr e molti altri ancora.

Squadre di ingegneri del software sono quindi state assoldate per risolvere questa tematica e tra le tante osservazioni prodotte una delle più importanti è stato il notare che la maggior parte delle più popolari applicazioni web a sfondo social potevano essere sviluppate anche senza un'utilizzo massiccio delle funzionalità relazionali tipiche dei RDBMS. Cominciano così a nascere tutta una serie di prodotti che, rinunciando a concetti come chiavi esterne, joins, database schemas, consentono di memorizzare in modo rapidissimo e distribuito strutture più o meno simili ad Hash {chiave:valore}.

In Facebook, dove già MySql veniva utilizzato in modo poco ortodosso, si decide di optare per una soluzione non relazionale nella tecnica di memorizzazione dei messaggi della Inbox. Nasce così Cassandra che dopo un periodo di gestazione viene resa pubblica su Google Code e successivamente incubata da Apache.

Logo di Cassandra

Con questo articolo, diamo il via ad una sorta di "workshop virtuale", proprio per questo nei prossimi paragrafi faremo una breve panoramica dell'architettura.

Principali caratteristiche tecniche

Cassandra è una database distribuito, fault-tolerant, elastico e con consistenza dei dati regolabile sia in read che in write; questo significa che il server è solitamente installato in una configurazione clustered nella quale più nodi di Cassandra cooperano per ottimizzare e distribuire le informazioni.

Nessun nodo è in alcun modo diverso dagli altri, in questo modo non esiste all'interno del cluster una specificità critica che in caso di malfunzionamento possa rendere inoperante il database.

I dati sono automaticamente duplicati su più nodi, questo garantisce che all'eventuale crash di un elemento dell'insieme non debba necessariamente seguitare la perdita di informazioni importanti o lo stallo dell'intera istanza di Cassandra.

Durante le operazioni di read/write, è possibile esplicitare il livello di consistenza che si vuole mantenere: l'impostazione si attua passando alla funzione di lettura/scrittura due parametri che indicano il comportamento atteso; le scelte disponibili variano per le funzioni di write:

  • 'esegui tutte le operazioni in asincrono' e conseguentemente non verificare l'effettiva scrittura del dato
  • 'scrivi e verifica la scrittura almeno sulla metà + 1 del numero di nodi indicati dalle impostazioni di fault-tolerance'
  • 'scrivi e verifica la scrittura su ogni nodo'

Analogamente esistono parametri simili per le funzioni di read che consentono di scegliere da quanti nodi effettuare la lettura prima di determinare quale sia la versione più fresca del dato richiesto.

Aumentando il numero di nodi da leggere, così come aumentando quelli da scrivere per quanto riguarda le funzioni di write, si ottiene un lineare miglioramento nella consistenza del dato a scapito delle performance: è quindi importante decidere con attenzione quale sia il miglior compromesso per la propria applicazione.

Installazione e configurazione

Cassandra è scritto in Java 1.6, per cui l'applicativo è interamente cross-platform e richiede la presenza del JRE come unico prerequisito. La procedura di installazione si risolve nel download dell'archivio dell'ultima versione stabile e nella sua decompressione in una cartella a piacere.

Nella sottocartella conf del prodotto risiedono due file log4j.properties e storage-conf.xml: nel primo dei due è necessario verificare che il percorso di salvataggio dei file di log specificato alla voce log4j.appender.R.File sia corretto ed esistente mentre nel secondo per il momento è sufficiente semplicemente modificare a piacere il testo racchiuso nel tag ClusterName in modo da identificare univocamente la propria istanza di Cassandra.

Nella cartella bin sono presenti gli eseguibili dell'applicativo: a seconda della propria architettura è necessario lanciare cassandra (Linux/OsX) o cassandra.bat (Windows) da un prompt dei comandi specificando il flag -f nel caso si voglia che il processo non vada in background.

bin/cassandra -f      # Da Mac/Linux
bincassandra.bat -f  # Da Windows

Se tutto va per il verso giusto sullo schermo compaiono una serie di messaggi riportanti il completamento dei vari step della procedura di bootstrap, dopodiché l'istanza di Cassandra resta in attesa di connessioni; in caso invece qualcosa vada storto esiste una succinta ma efficace procedura di troubleshooting che può aiutare nella risoluzione delle più comuni cause di errore.

Messaggi all'avvio del server

La procedura fin qui esposta può facoltativamente essere ripetuta su più macchine al fine di generare un vero e proprio cluster; in tal caso gli step da ripetere sono gli stessi appena elencati ai quali va aggiunto un'ulteriore passaggio di configurazione nel file conf/storage-conf.xml dove la sezione delimitata dal tag Seeds deve essere popolata con gli ip di alcune delle macchine del pool in modo che ogni macchina abbia nel file di configurazione almeno l'indirizzo di un altra. Tale necessità nasce dal fatto che il meccanismo di interscambio di informazioni di Cassandra, Gossip, impone che ogni nodo nel suo bootstrap debba contattare un altro elemento del cluster in modo da cooperare alla creazione di una rete ad anello che colleghi tutti i nodi.

Il Data Model

Il modello dati di Cassandra è al contempo semplice e complesso: si tratta essenzialmente di un aggregato di Hash e Array che però rivestono, a seconda della loro posizione, ruoli e funzioni ben distinte. A questo si aggiunge il fatto che la naming convention utilizzata sia molto fuorviante; è bene fermarsi quindi qualche secondo prima di proseguire per svuotare la mente da eventuali preconcetti derivanti dall'utilizzo di prodotti RDBMS.

Column

Cominciamo dal costrutto più semplice in assoluto, la column. Una column è un Hash formato dalle chiavi name, value e timestamp.

Esempi di column sono:

{
  name:  "age", 
  value: "26",
  timestamp: 123456743
}
{
  name:  "fullname",
	value: "Sandro Paganotti",
	timestamp: 836123423
}

Volendo fare un parallelo con un database relazionale, una column equivale ad una cella all'interno di un record che a sua volta è contenuto in una tabella. Da un punto di vista Object Oriented equivale invece ad un attributo di un oggetto (es: oggetto.fullname = 'Sandro Paganotti'). Nella pratica è semplicemente un Hash le cui chiavi sono stabilite a priori.

Ad eccezione del timestamp, che è per necessità un campo intero (i64), i valori delle chiavi name e value sono considerati da Cassandra come array di byte e quindi possono contenere praticamente qualsiasi cosa.

Supercolumn

Gruppi di column possono essere raggruppati all'interno di supercolumn, che altro non sono se non column il cui campo value contiene un array di column; un esempio di supercolumn potrebbe essere il seguente:

{
  name:  "telephone number",
  value: {
    {
      name:  "prefix",
      value: "349",
      timestamp: 123123141241
    },
    {
      name:  "number",
      value: "0516558976",
      timestamp: 	231231231434
    }
  }
}

Come potete notare, un'altra piccola differenza tra column e supercolumn è l'assenza in queste ultime del campo timestamp.

Tornando al parallelo con un RDBMS, non è facile indicare un equivalente per questa struttura: rappresenta ancora una singola cella di un record che però a sua volta contiene un valore composto, come se il contenuto fosse stato serializzato.

Prima di proseguire è necessario un cambio nella notazione utilizzata per questi esempi: l'obiettivo è quello di snellire la sintassi e contemporaneamente di dare il giusto risalto alla struttura ad Hash che poi verrà effettivamente usata nelle operazioni di inserimento e recupero dati; a tal fine l'esempio precedente riassunto nella nuova notazione assume questo aspetto:

telephone number: {
	prefix: "349",
	number: "0516558976"
}

Columnfamily e supercolumnfamily

column e supercolumn vengono raccolte in strutture chiamate rispettivamente columnfamily e supercolumnfamily che fanno le veci del costrutto 'tabella' dei database relazionali.

Una columnfamily è contraddistinta da un nome che la identifica e da un array di coppie chiave valore; ogni elemento di questo array è chiamato row ed è assolutamente affine al concetto di record dei RDBMS.

La chiave di una row funge da identificatore mentre il valore è a sua volta un array di tutti gli attributi del record in questione. Tali attributi possono essere soltanto column, nel caso in cui si stia definendo una columnfamily, o soltanto supercolumn, nel caso in cui il costrutto sia di tipo supercolumnfamily.

Ecco un esempio di columnfamily:

User = {
  sandropaganotti: {
    age: "26",
    email: "sandro.paganotti@gmail.com",
    gender: "male",
    username: "spx2"
  },
  jason: {
    age: "20",
    job title: "professional biker"
  }
}

Due osservazioni importanti: in primis non esiste nessuno schema predefinito per le singole row, ne in senso assoluto ne tra le row di una stessa family: ad esempio la row con chiave jason ha degli attributi solo in parte congruenti con quelli di sandropaganotti.

In secondo luogo l'esistenza o meno di una columnfamily/supercolumnfamily deve essere esplicitata nel già trattato file storage-conf.xml; non è quindi possibile creare questo tipo di struttura a runtime ma è necessario invece inserirle e documentarle all'interno del file di configurazione; ad esempio la dichiarazione della columnfamily User risulta essere:

<ColumnFamily CompareWith="BytesType" Name="User"/>

KeySpace

L'ultimo gradino in questa gerarchia di strutture dati è occupato dal keyspace, che ricopre lo stesso ruolo assegnato al database in ambito relazionale. Comunemente ogni keyspace contiene il set di columnfamily specifico di un'applicazione; anche questo tipo di struttura deve essere dichiarata nel file di configurazione all'interno del tag KeySpaces, ecco un esempio:

<Keyspace Name="Inventario">
  <ColumnFamily CompareWith="BytesType" Name="Scaffale"/>
  <ColumnFamily CompareWith="BytesType" Name="Oggetto"/>
  <ColumnFamily CompareWith="BytesType" Name="Tipologie"/>
</Keyspace>

L'attributo CompareWith indica il metro di ordinamento, sempre sulla chiave, mai sul valore, delle column/supercolumn all'interno delle singole row e offre lo spunto per un più approfondito ragionamento sulle procedure di inserimento ed estrazione dati in Cassandra.

Il database che stiamo analizzando, e in generale l'intera famiglia dei database nosql di cui questo prodotto fa parte, non integrano il concetto classico di query ma bensì un più generico set di API che consente alcune operazioni CRUD a cui si aggiunge la possibilità di prelevare particolari frammenti del database.

La maggior parte del lavoro di strutturazione, compreso l'ordinamento, viene invece effettuato in fase di scrittura, sia esso inserimento o modifica; questo comporta che il database deve conoscere in anticipo l'ordine da mantenere perché lo stesso deve essere applicato man mano che nuove column vengono create.

Nei prossimi giorni esamineremo l'intero features set di Cassandra lavorando direttamente sull'applicativo.

Ti consigliamo anche