2.2. Architettura Per utilizzare CockroachDB non è affatto necessario conoscere la struttura esatta e il suo modo di procedere. Comunque dalla documentazione ufficiale, per chi ne fosse interessato, è possible conoscere in dettaglio l’architettura di CockroachDB. L’architettura del Database può essere visto come una struttura a livelli, dove ogni livello vede quello sottostante come una scatola nera da poter utilizzare, mentre quelli sottostanti sono ignari di quelli superiori. La struttura viene divisa in 5 livelli 1) Livello SQL: permette all’utente di potersi connettere al database ed effettuare query Sql. In questo livello principalmente le query vengono tradotte in operazioni KV(chiave-valore) 2) Livello Transizionale: permette il supporto alle transizioni che rispettano le proprietà Acid, attraverso il coordinamento di operazione concorrenti 1 3) Livello Distributivo: permette di avere informazioni su come sono strutturati i dati del cluster. 4) Livello Di Replicazione: ha il compito di gestire le repliche dei range e di gestire come essi interagiscono attraverso il protocollo raft. 5) Livello di Memorizzazione: è il livello responsabile della scrittura e lettura dei dati. 2.2.1 Livello SQL Per potersi connettere a CockroachDB c’è bisogno solo di una connessione ad un nodo e delle query SQL. Uno sviluppatore può mandare una richiesta a qualsiasi nodo. Il nodo a cui si manda la richiesta funziona come nodo di gateway. Le query SQL raggiungono il proprio cluster attraverso il protocollo PostgreSql wire Protocol. CockroachDB permette un utilizzo dello Standard Ansi SQL attraverso le sue API SQL. Una volta ricevuta una query CockroachDb la analizza e crea un piano di esecuzione: • Parsing Quando arriva una query essa viene analizzata e confrontata con un file, YACC1 e converte ogni stringa in un AST, Abstract syntax tree. • Pianificazione Logica Poi la AST viene convertita in un piano di esecuzione attraverso 3 fasi 1) nella prima fase viene convertita in un piano di esecuzione ad alto livello oltre a quello viene eseguito un’analisi semantica, che include il controllo della validità della query. 2) il piano logico viene poi semplificato attraverso delle trasformazioni che permettono di ottimizzare la query. 3) il piano logico viene ottimizzato ultimando un algoritmo di ricerca che valuta i modi possibili di eseguire una query e seleziona il piano d’esecuzione con costo minore. • Pianificazione Fisica 2 La pianificazione fisica determina quali nodo parteciperanno all’esecuzione della query, sulla base della località dei range coinvolti, Infatti molto spesso CockroachDB decide dove mandare le query in base a dove le computazioni sono minori. • Esecuzione Vari componenti del piano fisico vengono inviati a uno o più nodi per l'esecuzione. Su ciascun nodo, CockroachDB genera un processore logico per calcolare una parte della query. I processori logici all'interno o tra i nodi comunicano tra loro attraverso un flusso logico di dati. I risultati combinati della query vengono rinviati al primo nodo in cui è stata ricevuta la query, per essere inviati ulteriormente al client SQL. DistSQL CockroachDB essendo un database distribuito, è stato sviluppato uno strumento di ottimizzazione Distributed SQL (DistSQL) per alcune query, che può velocizzare notevolmente le query che coinvolgono molti range. Nel caso in cui una query sia ottimizabile da DistSQL ciascun nodo esegue delle operazioni sulle righe e manda soltanto i risultati, invece delle intere righe, al nodo coordinatore, riducendo cosi il tempro per servire l’interrogazione. 2.2.2 Livello Transizionale La proprietà principale per un database è la consistenza. Per offrire consistenza CockroachDb il livello transizionale gestisce ogni statement come transizione. Per la funzione di scrittura non vengono inseriti direttamente sul disco, ma il livello transizionale origina: 1) Lock Replicati, o meglio conosciuti come “intent di scrittura”, per convenzione li scriveremmo come write intent, che mantiene un valore provvisorio ed a differenza del MVCC2 contiene anche un puntatore al record di transizione. 3 2) Record di transizione: cioè un record che contiene informazione sullo stato della specifica transizione. Una transizione si può trovare nello stato di pending, staging , committed o aborted. Se la transizione si trova nello stato di aborted essa dovrà essere generata di nuovo, al contrario se sta nello stato di commited si dovrà notificare all’utente l’esito dell’operazione. Una volta terminata la transizione, quindi passa dallo stato di staging a commited, il nodo coordinatore ha il compito di rendere le write intents come valori MVCC, rimuovendo il puntatore alla record di transizione. Se una transizione di sola lettura incontra dei valori solo MVCC allora la lettura può essere eseguita in modo normale. Altrimenti, se incontra una write intent, viene generato un conflitto di transizione, che dovrà essere risolto. Principalmente ci cono due tipi di conflitti: 1)Write/Write quando due transizioni nello stato pending creano degli write intents per la stessa chiave. 2)Write/read quando una read incontra una write intent con una timestamp minore del suo. Per risolvere i conflitti, CockroachDB procede nel seguente modo: 1) Se una transizione ha una priorità impostata (alto o basso) la transizione con la priorità più bassa viene abortita (nel caso write/write) oppure la sua timestamp viene spostata in avanti (Read/Read). 2) se una Transizione è scaduta, passa nello stato di aborted 4 3) considerando TxnA come una transizione e TxnB la transizione che incontra le write intent di TxnA TxnB viene messo in una coda di attesa e attende che TxnA viene completata. Poi se ci sono dei deadlock tra le transizioni, quindi nessuna delle transizioni può proseguire per colpa delle altre, ne viene scelta una a caso e viene abortita. 2.2.3 Distribution Layer Per rendere i dati accessibili da qualsiasi nodo del cluster CockroachDb conserva i dati attraverso una mappa ordinata (Monolithic sorted map).Tale struttura permette di identificare facilmente dove i vari dati sono localizzati all’interno del cluster. La mappa è composta da due elementi fondamentali: • I dati di sistema, Incluso nei Meta ranges che descrivono. la locazione dei dati all’interno del cluster • I data utente, che conservano i dati delle tabelle del cluster. Le posizioni di tutti i range nel cluster sono archiviati in un indice a due livelli, noto come meta range, dove il primo livello ci indirizza al secondo e il secondo ci indirizza ai dati del cluster. Ciascun Nodo consente di poter ritrovare dove è posizionato il primo meta range, ovvero meta1, il quale viene definito come il suo range descriptor. Invece i meta 5 range di secondo livello vengono definiti come Meta2 e loro ci indirizzano ai dati del cluster. Dopo i meta Range dei nodi, ci sono i dati KV(chiave valore) che il cluster salva. I meta range sono strutturati come una coppia KV. Entrambi i meta range hanno una simile struttura. metaX/successorKey -> LeaseholderAddress, [lista di altri nodi che contengono i dati] MetaX definisce a quale livello di meta range siamo 1 o 2. Successor Key la prima chiave più grande del range , ad esempio se il range va da [A-M) metteremo M, di solito viene utilizzato MaxKey per indicare la fine dello spazio delle chiavi. LeaseHolderAddress indirizzo del nodo che gestisce le varie repliche. Esempi: Meta1 contiene l’indirizzo per i nodi che contengono le repliche meta2 # Points to meta2 range for keys [A-M) meta1/M -> node1:26257, node2:26257, node3:26257 # Points to meta2 range for keys [M-Z] meta1/maxKey -> node4:26257, node5:26257, node6:26257 Meta2 contiene gli indirizzi per i nodi contenente le repliche di ciascun range nel cluster, la prima è il leaseholder 6 meta2/M -> node1:26257, node2:26257, node3:26257 Struttura della Tabella-Dati I dati chiave-valore, rappresentano i dati nella tabella utilizzando la seguente struttura: /<table Id>/<index id>/<indexed column values> -> <non-indexed/ STORING column values> O più nello specifico CREATE TABLE users (id INT PRIMARY KEY, name STRING, email STRING); INSERT INTO users (11, "Domenico", “[email protected]”); INSERT INTO users (13, "Carlo", “[email protected]“); /<tableid>/0/11/0 -> <empty> /<tableid>/0/11/1 -> "Domenico" /<tableid>/0/11/2 -> "[email protected]" /<tableid>/0/13/0 -> <empty> /<tableid>/0/13/1 -> "Carlo" /<tableid>/0/13/2 -> "[email protected]" Range Descriptors Ciascun Range all’interno di CockroachDB contiene metadati, conosciuti come range descriptor. Il range descriptor è strutturato nel modo seguente: • Id del Range • il keyspace, ovvero lo spazio delle chiavi • Gli indirizzi delle sue repliche e nello specifico il suo leaseholder. 7 2.2.4. Livello di replicazione Il livello di replicazione ha il compito di dover creare le repliche e distribuirle nei vari nodi all’interno del cluster. Il modo in cui vengono gestite le varie repliche fanno si che quando un nodo “cade” il sistema mantiene comunque il suo funzionamento. Quindi per poter garantire la disponibilità, CockroachDB permette comunque di poter lavorare nonostante un nodo sia andato offline. Per determinare il numero di fallimenti tollerati basta determinare questo valore (Replication factor3- 1)/2.). Ovviamente anzitutto deve essere assicurata la consistenza e per fare ciò CockroachDB utilizza un algoritmo di “consenso” chiamato Raft per far si che le varie repliche si concordino sul cambiamento di un range, prima che i essi vengono salvati. Raft organizza tutti i nodi che contengono le repliche di un range all’interno di un gruppo. Di queste repliche viene scelto un leader e quelle restanti diventano suoi follower. Il leader è responsabile poi di gestire tutti i write & read. Nel caso poi non ci dovessero essere delle risposte da parte del Leader, tutti i follower diventerebbero potenziali canditati a essere eletti come nuovo leader attraverso un sistema di votazione. 8 Quando un nodo riceve una BatchRequest per il range che esso contiene converte le operazioni KV in commandi Raft e poi le manda al leader del Raft. Tutti i commandi che sono stati mandati vengono inseriti nel Log del Raft. Il log consente ad un nodo che sia andato offline per un determinato periodo di tempo di essere aggiornato sullo stato del range senza dover aver bisogno di uno Snapshot[link] di una replica. Uno dei Nodi del cluster poi viene definito come come “LeaseHolder” ed è l’unico nodo che consente le read e propone le richieste di write al leader raft. Il ruolo del leaseHolder è importante nel caso delle read, infatti i write devono prima passare per il leaseholder, quindi l’ultimo write comittato è ancora valido e cosi quando c’è bisogno di una read non devono attendere i tempi di attesa del protocollo raft. Per poter poi migliorare le prestazioni conviene che il leaseHolder e leader siano lo stesso nodo. 2.2.4. Livello di memorizzazione Prima di introdurre il livello di memorizzazione o storage, vorrei introdurre RocksDB attraverso il quale CockroachDB conserva i dati. https://github.com/facebook/rocksdb/wiki/RocksDBBasics 9 RocksDB venne creato da Facebook e sviluppato in c++, ed è una fork della levelDB di Google. Viene considerato come un Database NoSql embedded. Infatti essendo un Database embedded CockroachDB lo può utilizzare come un storage engine(come tradurre). https://thenewstack.io/cockroachdb-unkillable-distributedsql-database/ CockroachDB contiene degli “store” attraverso i quali gli è consentito di poter leggere e scrivere i dati dal disco. In ogni store possono trovarsi tanti range. I dati vengono salvati come una coppia chiave e valore utilizzando RockDB. Ogni store può essere visto come 2 istanze di RockDB dove: 1) viene utilizzato per conservare i dati temporanei SQL distribuiti 2) per conservare altri dati all’interno del nodo. Anche i Dati MVCC vengono salvati con RockDB. Poi per poter gestire i vari store c’è una cache per tutti gli store del nodo. Ogni store poi come vedremo avrà delle repliche e ogni replica sarà di un range diverso. 10 11