NoSql

Appunti tradotti e riadattati del Prof. Pietro Colombo http://www.dicom.uninsubria.it/~pietro.colombo/

Indice



Slide14

Aggregation Pipeline

A framework for data aggregation based on data processing pipelines. 

Documents enter a multi-stage pipeline that transforms them. Pipeline stages do not need to produce one output document for every input document.

Stages can:

  • generate new documents
  • filter out documents

MongoDB provides the db.collection.aggregate() method in the mongo shell. Pipeline stages appear in an array.

Documents pass through the stages in sequence. Stages can appear multiple times in a pipeline.

>db.collection.aggregate( [ { <stage> }, ... ] )




Main aggregation stage operators


$match: Select the documents to pass unmodified into the next pipeline stage. For each input document, outputs either one document (a match) or zero documents (no match).


$project: Reshapes each document in the stream E.g., add new fields or remove existing fields. For each input document, outputs one document.


$unwind: Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements (n=0 for an empty array).


$group: Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields.


$sort: Reorders the document stream by a specified sort key. The documents remain unmodified. For each input document, outputs one document.

 

$limit : Passes the first n documents unmodified to the pipeline where n is the specified limit. For each input document, outputs either one document (for the first n documents) or zero documents (after the first n documents)

 

$skip: Skips the first n documents where n is the specified skip number and passes the remaining documents unmodified to the pipeline. For each input document, outputs either zero documents (for the first n documents) or one document (if after the first n documents).


$match

Filters the documents to pass only the documents that match the specified condition(s) to the next pipeline stage.

Syntax: { $match: { <query> } }

Example:

collection articles

{ "_id" : ObjectId("512bc95fe835e68f199c8686"), "author" : "dave", "score" : 80, "views" : 100 }

{ "_id" : ObjectId("55f5a192d4bede9ac365b257"), "author" : "ahn", "score" : 60, "views" : 1000 }

>db.articles.aggregate( [ { $match : { author : "dave" } } ] );

$project

Reshapes each document in the stream

Syntax: { $project: { <specifications> } }

Specifications:

  • <field>: <1 or true> Specify the inclusion of a field. [if the field does not exist no inclusion is performed ]
  • _id: <0 or false> Specify the suppression of the _id field.[only achievable with _id]
  • <field>: <expression> Add a new field or reset the value of an existing field.

{

"_id" : 1,

title: "abc123",

isbn: "0001122223334",

author: { last: "zzz", first: "aaa" },

copies: 5

}

 




$unwind

Deconstructs an array field from the input documents to output a document for each element.

Prototype form: { $unwind: <field path> }

E.g., collection inventory includes the document

{ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] }

 

> db.inventory.aggregate( [ { $unwind : "$sizes" } ] )

Restituisce:


{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }

$group

Groups documents by some specified expression and outputs to the next stage a document for each distinct grouping.

Prototype: { $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }

  •  _id specifies the mandatory grouping key
  •  <accumulator> specifies the aggregation operation

{"$group" : {"_id" : "$author", "count" : {"$sum" : 1}}}


Main accumulator operators

$sum Returns a sum for each group. Ignores non-numeric values.

$avg Returns an average for each group. Ignores non-numeric values.

$first/$last Returns a value from the first/last document for each group. Order is only defined if the documents are in a defined order.

$min/$max Returns the lowest/highest expression value for each group.

$push Returns an array of expression values for each group.

$addToSet Returns an array of unique expression values for each group. The order in the array is undefined.

$group example, collection sales:

{ "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-03-01T08:00:00Z") }

{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : ISODate("2014-03-01T09:00:00Z") } 

Calculate total price and avarage quantity by date

>db.sales.aggregate( [

{$group :

{ _id : { month: { $month: "$date" },

day: { $dayOfMonth: "$date" },

year: { $year: "$date" } },

totalPrice: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }}}

])






Slide15

Indexes

Gli indici sono la chiave per migliorare le prestazioni su MongoDB; senza indici MongoDB deve scansionare ogni documento in una raccolta per selezionare i documenti corrispondenti.

Gli indici sono memorizzati in alcuni campi in una forma facilmente accessibile.

I valori degli indici sono memorizzati ordinatamente. Gli indici sono definiti per collezione.

Scopo:

  • Velocizzare le query comuni
  • Ottimizzare delle specifiche query



L'indice può essere attraversato da destra a sinistra o viceversa per restituire risultati ordinati (senza bisogno di essere ordinato)




Eseguendo una query Mongo non ha bisogno di ispezionare i dati se sono tutti compresi nell'indice




Index Types

- Default: _id

  • Esiste di default, quindi se non e specificato _id, è creato
  • Unico

- Singolo campo

  • Definito dall'utente, su un singolo campo di un documento

- Compound

  • è definito dall'utente su più campi

- Multikey index

  • Per indicizzare il contenuto di un arrey
  • Crea indici separati, per ogni elemento dell'arrey

- Ordered Index

  • B-Tree

- Hash Indexes

  • Fast O(1) è un indice molto veloce e indicizza l'hash di un campo;( vale solamente per quando cerco valori uguali)

 - Geospatial Index

  • 2d indexes = use planar geometry when returning results ■ For data representing points on a two-dimensional plane
  • 2sphere indexes = spherical (Earth-like) geometry, For data representing longitude, latitude

- Text Indexes(per cercare stringhe contenute in una collezione)

explain

Il comando explain() può essere usato per "vedere" come MongoDB usegue la query

 

> db.users.find({username: "user101"}).explain("executionStats")

{

..."totalDocsExamined" : 1000000, //number of accessed documents

... "nReturned" : 1, //number of documents composing the result

..."millis" : 721, //execution time

}

Indice su un singolo campo

Permette di ottimizzare query simili, per creare un indice su un singolo campo ad es. con 'username'

> db.users.createIndex({"username" : 1})

Una volta che l'indice è stato creato e ripetendo le query:

>db.users.find({"username":"user101"}).explain("executionStats")

> db.users.find({username: "user999999"}).explain("executionStats")

Abbiamo un gran differenza di tempo di esecuzione. Questa velocità ha il suo prezzo:

  • Ogni scrittura impiegherà più tempo di esecuzione...
  •  Gli indici devono essere aggiornati ogni volta che i dati cambiano

Si possono creare fino a 64 indici per ogni collezione, ma sono raccomandati di non usarne più di 2

Quali campi usare come indice? Bisogna analizzare le query di quella particolare applicazione

Un indice tiene tutti i suoi valori ordinatamente:

  • ordinare i documenti che sono indicizzati è molto più veloce
  • useful for sorting only if it is a prefix of the sort

Non è utile per ordinare composti da più chiavi:

> db.users.find().sort({"age" : 1, "username" : 1})

Questa query ordina per età e username …

Indici composti

Un indice composto da più di un campo, è utile quando abbiamo query che devono ordinare tra più campi

Ad es. un indice composto era ottimizzato per la query precendente:

> db.users.createIndex({"age" : 1, "username" : 1})

L'indice potrebbe assomigliare a qualcosa del genere:

  • [0, "user100309"] -> 0x0c965148
  • [1, "user100156"] -> 0xf78d5bdd
  • [1, "user100187"] -> 0x68ab28bd
  • [2, "user100141"] -> 0x3996dd46
  • [2, "user100149"] -> 0xfce68412

Ogni riga dell'indice specifica età e username e punta ad un documento. Il modo in cui poi viene calcolatò l'indice dipende dal tipo di query

Point query

>db.users.find({"age" : 21}).sort({"username" : -1})

Quando cerco per dei documenti per un singolo valore, ma tanti documenti hanno quel valore

  • Visto che è un campo secondario nell'indice, i risultati sono già ordinati
  • Non c'è quindi bisogno di ordinarli
  • Indipendenza dalla direzione dell'ordinamento

Multi value query without sort

>db.users.find({"age" : {"$gte" : 21, "$lte" : 30}})

Quando cerco dei documenti che devono soddisfare più condizioni

Il primo indice "age",è usato per ritornare i valori che soddisfano la condizione

[21, "user100000"] -> 0x37555a81

[21, "user100069"] -> 0x6951d16f

...

[30, "user999775"] -> 0x45182d8c

[30, "user999850"] -> 0x1df279e9

Grazie all'indice secondario i documenti sono già ordinati (per username)


>db.users.find({"age" : {"$gte" : 21, "$lte" : 30}}).sort({"username" :1})

L'indice potrebbe assomigliare a qualcosa del genere:

[21, "user100000"] -> 0x37555a81

[21, "user100069"] -> 0x6951d16f

...

[22, "user100004"] -> 0x81e862c5

[22, "user100328"] -> 0x83376384

...

Visto che vogliamo i dati ordinati per username ma vengono restituiri dalla prima parte della query per età, l'ordinamento avverà in memoria.

Query Selectivity

"Query selectivity" è la misura di quando i predicati estraggono i documenti da una collezione

Is an expression that evaluates to TRUE, FALSE, or UNKNOWN. Predicates are used in the search condition of WHERE clauses and HAVING clauses, the join conditions of FROM clauses, and other constructs where a Boolean value is required.

è una misura utile per capire se una query usa gli indici efficacemente. Più una query è selettiva minore sarà la percentuale di documenti.

ad esempio una query che ricerca l'_id del documento sarà molto selettiva

Query poco selettive che trovano una grande quantità di documenti, non usano gli indici efficacemente

Ad esempio gli operatori $nin e $ne solitamente trovano una gran quantità di documenti; spesso indici su indici che usano $nin e $ne non portano a migliori performance, in quanto cercano in tutti i documenti.

La selettività di un espressione regolare dipende dalla espressione regolare

Covered Query

Un indice copre una query quando:

  • tutti i campi della query fanno parte dell'indice
  • tutti i campi selezionati fanno parte dello stesso indice

ad esempio una collezioni di prodotti che ha il seguente idice:

>db.inventory.createIndex( { type: 1, item: 1 } )

Questo indice copre la seguente query

>db.inventory.find({ type: "food", item:/^c/ },{ item: 1, _id: 0 })

Quando l'indice contiene tutti i campi della query:

  • Mongo può trovare subito le condizioni che soddisfano la query usando solamente l'indice
  • Viene eseguita molto più rapidamente che cercando fuori dall'indice

Restrictions on Indexed Fields

Un indice non copre una query quando:

  • qualsiasi dei campi di un indice in qualsiasi documento include un arrey
  • any of the indexed field in the query predicate or returned in the projection are fields in embedded documents.

es. considera una collezione con la seguente struttura:

{ _id: 1, user: { login: "tester" } }

La collezione ha un indice: { "user.login": 1}

L'indice non copre la seguente query: 

db.users.find( { "user.login": "tester" }, { "user.login": 1, _id: 0 } )

Comunque, la query può usare { "user.login": 1 } l'indice per trovare i documenti

Quando non creare un indice

Gli indice sono più efficaci se coprono una piccola quantità di dati, sono meno efficaci quando vengono usati sulla maggior parte dei dati

Per assurdo se una query deve prendere tutti i dati da una collezione, l'esecuzione sarà più lenta di scansire tutta la collezione perchè gli indici hanno bisogno di 2 lookups

  • Guardare tutto l'indice
  • Seguire il puntatore dell'indice nel documento

La scansione completa comporta solamente la scansione dell'intero documento

Non ci sono regole per determinare quando un indice aiuta o è di ostacolo, ci sono troppe variabili

size of data, size of indexes, size of documents, average result set size

Se una query ritorna più del 30% dell'intera collezione si può iniziare a vedere se è più efficente una semplice scansione nella collezione






Slide16

Data modeling Column family datastores

Le strutte di un Data modeling Column family sono:

  • Keyspace
  • Row key
  • Column
  • Column family

Keyspace

è la radice, i contenitore che contiene i column families, row keys, e related data structures

Tutti le altre strutture sono contenute in Keyspace

Di solito c'è un solo Keyspace per applicazione


Row key

L'indentificativo di una riga in un column family

Ha la stessa funzione di della chiave primaria in un db relazionale

è usato per partizionare e ordinare i dati:

  • In HBase, rows are stored in lexicographic order
  • In Cassandra, rows are stored in an order determined by the partitioner


Column

A data structure for storing a single value:

  • Values may be simple string of bytes (e.g., HBase) as this does not require data types validation
  • Values may refer to data types ranging from integers and strings to lists and maps. (e.g., 20 data types Cassandra).

Characterized by:

  • A column name
  • A time stamp or other version stamp (Allows the database to store multiple values associated with a column while maintaining the ability to readily identify the latest value. different types of version control mechanisms used)
  • A value

A column, along with a row key and version stamp, uniquely identifies values



Column family

Collections of related columns, Columns that are frequently used together should be grouped into the same column family

Column families are stored in a keyspace. Each row in a column family is uniquely identified by a row key. Similar to tables within RDBMSs, but:

  • RDBMSs do not necessarily maintain an order within tables
  • Within RDBMSs all tuples have the same columns

es. Store multiple rows and multiple columns


Design strategies

Il design di come modellare le tabelle si basa sulle query. Sono loro infatti che forniscono le informazioni necessarie per modellare un Column family

1) Entità: sono modellate come righe, identificate da un RowKey

2) Attributi delle entità: Sono modellate come colonne (riferite dalle query di selezione e proiezione)


Query criteria:

  • Il criterio di selezione ci suggerisce come organizzare i dati in tabelle e partizioni
  • Il criterio di selezione ci suggerisce come raggruppare gli attributi nei colum families

Valori derivati, possono essere ulteriori valori che vogliamo salvare

Differenze con il RDBMSs.

Column family sono implemetati differentemente:

  • Column family databases sono implementati come mappe multidimensionali scarsamente popolate
  • Columns possono cambiare tra le varie righe
  • Columns possono essere aggiunte dinamicamente
  • Joins non sono usati, si usa la denormalizzazione

Guide linea per modellare le tabelle

Joins non sono usati, si usa la denormalizzazione

Usare la tecnica "valueless colums". (The name of your column becomes the relevant information & the value of the "name/value" pair is empty)

Usa sia il nome della colonna che il valore per salvare i dati

Modellare un entità come una singola riga

Mantenere un appropriato numero di "column value versione"

Evitare dati complessi in "column values"

Denormalizzazione

Le tabelle modellano le entità. è prevedibile avere una tabella per entità

Solitamente colums store necessità di meno tabelle, visto che la denormalizzazione ci permette di evitare i join

Una relazione MoltiAMolti nei database relazionali necessita di 3 tabelle

  • 2 per le entità
  • una per rappresentare la relazione tra esse

Una singola tabella può essere usata nei colum family

Es. Cust_Prod tiene traccia della relazione tra utenti e prodotti



Many to many relations

A set of column names referring the purchased products



Products include a list of customer IDs specifying the customers who bought the product

Solution 1 valueless columns

Instead of having a column 'ProductPurchased1' with value 'PR _ B1839', the table stores the product ID 'PR _ B1839' as the column name. No need for associated values

Using column names to store data can have advantages:

In Cassandra, data stored in column names are stored in sort order, but data stored in column values are not.

A value may be associated with a column name ○ E.g., the presence of property (T, F)

Solution 2 - column names and values

Use the column value for denormalization

Example. Product features like description, size, color, and weight in the products table

To list products bought by a customer → add the product name as column value, in addition to its identifier, specified as column name 

No need to join the tables to produce the report





esempio fatto bene

Abbiamo un sistema relazionale modellato come nell'immagine:



Vorremmo fare queste query:

  • Tutti gli utenti a cui piace quel particolare oggetto
  • Tutti gli oggetti che piacciono a quel particolare utente

Ci sono 2 modi per denormalizzare la relazione in un ColumFamily datastore

1) denormalizzare le entità con un indice personalizzato



2) Normalizzare le entità con una denormalizzazione in un indice personalizzato



Slide18

Modellare entità

Porta ad avere righe con diverse colonne

  • I prodotti nel nostro esempio hanno delle caratteristiche in comume (ID,price,SKU)
  • I prodotti però hanno tra di loro differenti attributi


Attributi in tabelle differenti

I column family datastore non hanno lo stesso livello di controllo delle transazioni dei DB relazionali

Di solito infatti la scrittura di una riga avviene in maniera atomica, quindi se dobbiamo fare un update di più colonne o tutte andranno a buon fine oppure nessuna

Un upload a più tabelle può quindi portare ad inconsistenza nei dati

es supponiamo che vogliamo aggiornare la tabella dei prodotti e la tabella dei libri

Se l'update nella tabella dei prodotti viene completata con successo mentre quella nella tabella libri fallisce si avrà → inconsistenza


Hotspotting in row keys

Hotspotting capita quando tante operazioni sono eseguite su un ristretto numero di nodi, mentre il resto dei cluster è poco utilizzato

HBase usa un ordinamento lessografico per ordinare le rowKey. Ciò permettte:

  • Diminuire le latenze sul disco
  • Può portere a continuare a usare sempre lo stesso nodo mentre gli altri sono sotto-utilizzati

Column value versions

Alcuni database, come HBase, permettono di salvare più versioni dello stesso valore. I valori sono

  • Timestam, così si riesce a determinare facilmente l'ultima modifica
  • Utile per tornare ad una situazione precedente

As many versions as requirements dictate, but no more

HBase permette di fissare un numero di versioni da tenere in memoria, implemetando un buffer circolare

HBase:

  • Ogni valore è associatp con il proprio timestam
  • Più versioni



No complex data structures

I column data family permettono di salvare oggetti Json, ma non è consigliato; consigliato quindi usare la stringa

Se invece si deve effettuare qualche operazioni sull'oggetto, meglio decomporre la struttura

Separate columns per attribute:

  • allow using column families
  • Favor the use of secondary indexes

Meglio separare in colonne: strada città, stato, zip e permettere la definizione di un indice secondario



Guidelines for indexing

Gli indici permetto rapidamente di fare un lookup dei dati in tabella

SELECT fname, lname FROM customers WHERE state = ‘OR’

Gli indici permettono di recuperare le informazioni più velocemente, questa miglior performance viene raggiunta con un incremento dello spazio occupato

Index types

Gli indici primari sono sulla rowKey di una tabella, e sono automaticamente mantenuti dal sitema

Gli indice secondari sono creati su una o più colonne

  • Sia direttamente il DB o l'utente può creare e gestire gli indici secondari
  • Non tutti i DB forniscono la possibilità di avere un indice secondario gestito dal sistema

you can create and manage tables as secondary indexes in all column family database systems

Automatically managed indexes

Richiedono minor codice per essere mantenuti. Es in cassandra il codice:

CREATE INDEX state ON customers(state)

  • Crea un indice
  • Mantiene tutte le necessarie strutture
  • Determina l'uso ottimale dell'indice

Per esempio nella seguente query, ipotizzando di avere come indice "state" e "Iname":

SELECT fname, lname FROM customers WHERE state = ‘OR’ AND lname = ‘Smith’

Cassandra scegli che indice usare per primo, di solito usa il più selettivo

Nel nostro esempio con 15000 persone in oregon e 200 chiamate Smit scegliera prima di usare l'indice sul nome

Bisognerebbe evitare di usare un indice (o fare dei test) se:

  • C'è un ridotto numero di valori unici nelle colonne
  • Ci sono tanti valori unici in una colonna
  • I valori nella colonna sono pochi

Example 1. In una colonna ci sono un numero quasi uguale di si e no



Esempio 2. Non porta benefici se la cardinalità è troppo alta, ad esempio nella colonna mail. Più efficiente usare una ricerca su tutta la colonna senza indice




Esempio 3. Non utile se molti utenti non usano quella colonna

  • Quasi tutti gli utenti vivono in US ed hanno la provincia
  • Gli utenti canadesi non hanno la colonna della provincia



Indexes managed using tables

é utile quando

  1. il column family non supporta automaticamente l'indice secondario
  2. la colonna da indicizzare ha molti valori distinti

Richiede tabelle che le tabelle che salvano i dati siano accedute trami l'indice

Example

Indice che supporta queste query

  • Lista di tutti gli utenti che hanno comprato un particolare prodotto
  • Lista di tutti







Slide19

Indexes managed using tables

è utile quando:

  • quando il colums family non supporta automaticamente l'indice secondario
  • la colonna da indicizzare ha molti valori unici

Richiede che la tabella dove sono salvati i valori sia acceduta tramite l'indice

The indexes refer to the same table, column family, and column data structures used to store your data.

Esempio 1. Indici che supportano queste query:

  • Lista di tutti gli utenti che hanno acquistato un particolare prodotto
  • Trovare tutti gli utenti che hanno acquistato quel particolare prodotto



Scopo:

  • trovare subito le informazioni di uno specifico prodotto (name and description)
  • Trovare i clienti che hanno comprato quel prodotto

Strategia:

  • Una tabella che usa prodoctID come rowKey. Come nome della colonna l'ID del cliente
  • Il valore della colonna può salvare informazioni aggiuntive come il nome fname



Esempio 2 Elencare tutti i prodotti acquistati da un utente

  • il userID sarà il nostro rowKey
  • prodotto ID sarà indicato tramite il nome della colonne

Il valore della colonna invece salverà il nome del prodetto




Indexes managed using tables

L'utente è responsabile di tenere aggiornato l'indice

2 strategie

a. aggiornare l'indice ogni volta che c'è un cambiamento nei dati. (es quando un cliente acquista qualcosa)

  • pro: l'indice è aggiornato sempre
  • contro: lunga latenza se più scritture avvengono nello stesso momento

 

b. avere un job che ad intervalli regolari aggiorna l'indice

  • Pros: non incrementa la latenza
  • Cons: ci possono essere dei periodi in cui indice e dati non sono sincronizzati

 

Le scelte sono dettate anche dalle specifiche

a. When queries are executed









Slide19b

Principi nella distribuzione dei dati e consistenza

La scalabilità in un sistema

Scalabilità significa poter gestire un continuo aumento dei dati e delle query senza perdere in performance.

Ci sono 2 possibili approcci

  • Verticale
  • Orizzontale

Scalabilità verticale

Implica grandi e più potenti mezzi hardware:

  • Più grandi e capienti dischi
  • Usare architetture parallele
  • Più grandi memorie

è la scelta tradizionale, favorisce una forte consistenza dei dati ed è semplice da realizzare.

Ha però:

  • Costi alti (un supepr-server costa più che un computer con "hardware base")
  • Dati hanno un limite (lavorano bene finche i dati sono entro una certa soglia)
  • Previsioni (all'inizio dello sviluppo non si sa effettivamente la grandezza finale)
  • I costi per l'hardware sono immediati nella scalarità verticale
  • Solo pochi costruttori di hardware per "super-server"

Scalabilità orizzonale

Nella scalavilità orizzonale il sistema è distribuito su più macchine/nodi

Ongi macchina può avere un hardware relativamente economico

Nell'approccio orizzonale:

  • I dati sono partizionati in più dischi
  • L'applicazione può usare la memoria di tutte le macchine
  • Modello computazionalmente distribuito

Il modello verticale introduce nuovi problemi: sincronizzazione, consistenza, errori parziali, ...

Le tipiche false ipotesi che si hanno su di un sistema distribuito sono:

  • La rete si affidabile
  • La latenza è zero
  • La banda sia infinita
  • Il network sia sicuro
  • Il network si omogeneo
  • La topologia della rete non cambia
  • Ci sia solamente un amministratore

Ci sono 2 modi per distribuire i dati

  • Replicazione
  • Sharding

Replicazione: lo stesso dato è copiato più volte in più nodi, master e slave - peer to peer

Sharding: il dato è spezzato in "data chunks" ed è sparso in diversi nodi

Questi 2 modi si possono usare insieme oppure combinarli

Server singlolo

Far girare il database su una singola macchina è sempre il miglior scenario, infatti evita un sacco di problemi

Può quindi avere senso usare un database NOSQL su un singolo server:

  • Flessibilità dei dati, semplicità
  • Per i "graph database" il grafico è difficile distribuirlo so più nodi.

Sharding (data partitioning)

è la possibità di poter avere parti di dato in differenti server.

Differenti persone possono accedere a differenti parti del dataset




Possibilità di avere un "Auto" o "Manual" sharding

Ci sono 3 modi per distribuire i dati

  • Range
  • Hash
  • Entity group

Range sharding

Si partizionano i dati con valori coninui ed ordinati

Utile per fare "range-query"

Richiede:

  • Coordinamento attraverso un master che gestisce gli assegnamenti
  • Individuare e risolvere gli "hotspots"

Hash sharding

Il dato viene assegnato allo shard in base alla funzione di hash derivata dalla chiave primaria

Non ci vuole coordinamento

Permette un semplice lookup, di contro però non permette range query

Entity group sharding

Partiziona i dati con l'obbiettivo di abilitare una transizione su di una singola partizione di dato "co-locate"

Raccomandazioni

Bisognerebbe che:

  • Dati a cui si accede contemporaneamente siano tenuti insieme, l'utente può trovare quello che cerca in un singolo nodo [Aggregate data model aiutara a raggiungere questo obbiettivo]
  • Organizzare i dati nel nodo in base alla posizione fisica e al carico di lavoro

La caduta di un nodo provoca i dati dello shard inraggiungibili, per questo che viene spesso combinato con la replicazione

Strategie di replicazione

Possiamo avere 2 variabili: "Quando" e "Dove". Quando gli update sono propagari alle repliche e dove gli update sono accettati.

Quando

Eager (sincrono) propaga immediatamente i cambiamenti a tutte le sue repliche prima che la risposta sia data al client

Consistenza del dato contro una alta latenza

Lazy (asincrono) I cambiamenti avvengono direttamente nella replica e poi vengono passati asincronamente.

Migliori performance ma un dato inconsistente può essere letto.

Dove

Master-slave le modifiche sono accettate solamente dal master, ciò comporta minore complessità

Peer to peer le modifiche sono accettate da tutte le repliche, grande complessità per la concorrenze e necessità di dover prevenire conflitti.

Ci sono varie tecniche per gestire una rete peer-to-peer: versioning, vector clock, gossiping, read repair

Combinando quando e dove si possono ottenere:

  • Eager master-slave: RDBMS nei sistemi distribuiti
  • Eager update anyware: poco usato
  • Lasy master-slave: Mongo (CP system)
  • Lasy update anyware: (AP system)

Se il sistema NoSql lo permette può venir lasciata la scelta all'utente:

  • Una risposta veloce per minimizzare la latenza
  • Una risposta consistente per prevenire dati inconsistenti





Slide20

Replica "Master e Slave"

I dati sono replicati attraverso più nodi




Un solo nodo è responsabile (master) di processare tutti gli update, gli altri nodi sono secondari.

La lettura può avvenire su tutti i nodi

Per applicazioni ad alta intensità di lettura

All'aumentare delle richieste di lettura -> più nodi slave

Si ha una "lettura-resiliente" infatti se il master è inraggiungibile, gli slave posso processare richeste di lettura

Uno slave può diventare velocemente un master

Il sistema viene limitato dalla quantità di update che il master riesce a processare

Il master può essere scelto manualmente o automaticamente

Peer-to-peer replication

Nessun master, ogni replica è uguale all'altra. Ogni nodo può gestire la scrittura e propagare l'update agli altri nodi




Nasce però il problema della consistenza. I client possono scrivere contemporaneamente su più nodi.

Quando c'è una scrittura, i nodi replicanti si coordinano per evitare i conflitti. Non tutti i nodi devono essere d'accordo, basta la maggioranza.

Shard e master-slave

In un sistema shard e master-slave, ogni dato shard è replicato. Ogni nodo può essere master per alcuni dati e slave per altri.




Shard e peer-to-peer

In questo sistema ogni shard è tipicamente prensete in 3 nodi, è il sistema usato di solito dai colum-family datacomum database

Ci possono essere problemi di consistenza




Consistency nei database

I database relazionali RDBMS assicurano una forte consistenza dei dati

I database distribuiti NoSql invece allentano il vincolo della consistenza, si passa quindi da "Strong Consistency" [ACID] a "Eventual Consistency"[BASE]

Viene affrontato nel teorema CAP - c'è da fare una scelta tra consistenza e disponibilità

Consistenza in scrittura

[write-write conflict]

Problema: 2 utenti fanno un update dello stesso record nello stesso instante. Ci potrebbere essere un problema in quanto in secondo update si base si un dato non consistente

Soluzioni

Approccio pessimistico: previene i conflitti con un semaforo prima dell'update

Approccio ottimistico: lascia che i conflitti possano succedere, e prova a risolverli se capitano.

Un esempio potrebbe essere un update condizionato, ogni client testa il valore che vorrebbe modificare; se è differente l'update fallisce




Consistenza in lettura

[read-write conflict]

Un utente legge nel mezzo di una scrittura di un altro utente.




La soluzione ideale: ACID, ovvero strong consistency.

Però NoSqlDatabase permettono atomic updates solamente a livello di singolo aggregato

Se abbiamo un update che implica diversi valori in più aggregati questo può portare a un time-slot in cui un cliente può leggere un valore inconsistente (inconsistency windows)

Transaction in NoSql non ci sono problemi se il DB è centralizzato, più complicato per sistemi distribuiti

Replication consistency

La consistenza tra le repliche ci assicura che il dato ha lo stesso valore quando viene lette da differenti nodi.

Eventuale consistenza significa che la scrittura si propagherà nella rete, quindi i dati passano nello stato di eventualemnte inconsistenza

Ci sono vari livelli di inconsistenza




Teorema CAP

C = consistency A = availability P = Partition Tollerance

Consistency

Dopo un update tutti i client del sistema vedono lo stesso dato

Un singolo database è sempre consistente, se occurrono diverse istante di scrittura e lettura il sistema deve trattarle in modo speciale

Availability

Se un nodo sta lavorando deve poter leggere e scrivere, quindi ogni richiesta deve essere processata

Partition Tollerance

Significa che il sistema deve continuare a funzionare anche se due insiemi di server vengono isolati. Una connessione tra server fallita non dovrebbe pregiudicare l'intero sistema

Il teorema CAP afferma che un sistema non può avere tutte e 3 le proprietà, solamente 2 su 3 sono possibili

Nelle normali condizioni un sistema può essere sia disponibile che consistente. In presenza di una partizione, ciò non è più possibile. Un nodo deve decidere:

  • continuare a processare le richieste del cliente, preservando la disponibilità alla consistenza
  • non processare la richiesta, preservando la consistenza alla disponibilità

Riassumento

  • AP - viola la consistenza
  • CP - viola la disponibilità
  • CA - sono disponibili e conistenti ma falliscono completamente se c'è una partizione



Un singolo server è sempre CA

Un sistema distribuito deve tollerare una partizione della rete.

Deve essere presa la decisione se scegliere la consistenza o la disponibilità

CP - consistenza e tolleranza alla partizione

Esempio. 2 utenti 2 nodi e 2 scritture




Consistenza forte, prima di ogni scrittura i nodi devono essere d'accordo sull'ordine delle scritture

In caso di partizionamento si perte la disponibilità in scrittura, in lettura è ancora possibile




Per poter aggiungere un pò di avalability si può avere una "master-slave" replicazione




Ora in caso di partizione il master può effettuare la scrittura ma i dati sullo slave in lettura saranno inconsistenti




AP - disponibilità e tolleranza alla partizione

usando un modello peer-to-peer replications, in caso di partizione si ha eventuale inconsistenza dei dati




Si può quindi ovviare al problema con un soluzione intermedia: Quorums

Quorums

Nei sistemi peer-to-peer con la replica fattore N (n volte replicato il dato)

Quorum in scrittura: quando avviene una scrittura almento W repliche devono essere d'accordo

Avendo W > N/2 si ha la consistenza in scrittura

Esempio. 3 Nodi, W = 2 . Almeno 2 sui 3 nodi devono essere d'accordo con la scrittura




In caso di 2 scritture simultanee solamente 1 scrittura avrà la maggioranza

Slide21

Read quorum

Read quorum: R is the number of peers contacted for a single read

Assuming that each value has a timestamp (time of write) to tell the older value from the newer

For a strong read consistency: R + W > N [reader surely does not read stale data]

Example read quorum with R = 2 (R + W > N)

2 nodes contacted for read => the newest data returned




Relaxing Durability


Durability: when Write is committed, the change is permanent.  In some cases, strict durability is not essential and it can be traded for scalability (write performance)

ex storing session data, collection sensor data

A simple way to relax durability: Store data in memory and flush to disk regularly, if the system shuts down, we lose updates in memory

BASE Concept

BASE: a (vague) term often used as contrast to ACID

● Basically Available

  • The system works basically all the time
  • Partial failures can occur, but without total system failure

● Soft state

  • The system is in flux (unstable), non-deterministic state
  • Changes occur all the time

● Eventual consistency

  • The system will be in some consistent state
  • At some time in future

Summary

- Sharding vs. replication

  •  Master-slave vs. peer-to-peer replication, a combination of sharding & replication

- Database consistency:

  • Write/Read consistency (write-write & write-read conflict), replication consistency (also, read-your-own-writes)

● Relaxing consistency:

  • CAP (Consistency, Availability, Tolerance to Partitions), Eventual consistency, BASE
  • Quoras (write/read quorum), can ensure strong replication consistency; wide range of settings
Slide21b

Data distribution patterns

Solitamente i sistemi non relazionali non prevedono che siano rispettate le proprietà "ACID", si focalizzano infatti su altri aspetti:

  • Bilanciamento tra disponibilità e consistenza
  • Costi dell'hardware più contenuti, scalabilità orizzontale
  • Resilienza: in caso di un guasto ad un nodo ci potrebbere essere sia continuità di servizio che la mancanza di perdita di dati

Architettura MongoDB




Il processo router (3) è resposabile per indirizzare le richiesta al giusto shard server.

Gli shard sono implementati con MongoDB database, non sanno il loro ruolo all'interno del server shared(1)

Il server di configurazione (2) contiene i metadata necessari per capire come i dati sono distribuiti i dati tra i vari shard

In MongoDB le collezioni sono formati da documenti JSON con attributi in comune

Le collezioni sono le unità elementari della distribuzione dei dati

Per dividere la collezione tra più shard bisogna scegliere una shared key

la shared key è un indice immutabile, composto da un unico campo o più campi ma che esiste in ogni documento della collezione

La shared key una volta scelta non si può più cambiare. la scelta della chiave influisce:

  • Sulle performance e sulla scalabilità del sistema
  • Influenza la strategia di sharding dei dati

Mongo sharding mechanism

Mongo partiziona i dati condivisi tra i vari shard in chunks

Ogni chunk ha 2 valori di riferimanto, uno basso ed uno alto, un processo "balancer" gira in background per migrare chunk tra i vari shards e cercare di avere una distribuzione equa

Vantaggi dello sharding

MongoDB distribuisce il carico di letture/scritture attraverso i vari shard, facendo fare ad ognuno un pezzettino di lavoro di cluster

Il carico può essere scalato orizzontalmente su tutto il cluster aggiungendo dei nodi

Se l'attributo di riferimento di una singola query si riferisce ad un singolo shard mongo indirizza la richiesta a quello specifico shard

Un altro vantaggio dello sharding è la possibilità di aggiungere shard al crescere dei dati

Un cluster può avere maggior availability

  • Ci possono effettuare parziali scritture / letture anche se qualche nodo della rete è guasto
  • Letture o scritture dirette agli shard disponibili sono possibili
  • Un cluster con shard configurati con Server Replica Set può continuare a processare letture e scritture per tutto il tempo che la maggior parte dei nodi è disponibile

Un database può avere un mix di shared e non shared collezioni




In questo esempio di cluser le shared collection sono distribuite tra i vari shard, mentre le collection non distribuite sono salvate direttamente in un unico shard.

Strategie per lo sharding

La distribuzione dei dati tra i vari shard può essere fatta:

  • Range base
  • Hash base

Range base: ogni shard ha uno specifica serie di valori della shard key. é più efficente in caso di non monolitica changing shared keys

Hash base: è ideale per quelle chiavi che cambiano monotonicamente come _id o un timestamp

La configurazione hashed sharding richiede di computare una funzione di hash ogni volta che viene richiesto il campo shard key. Ad ogni chank è assegnato un range di valori.

Mongo automaticamente risolve e computa gli hash quando risolve una query




La distribuzione dei dati attraverso i valori di hash facilità la distribuzione, specialmente con quei dati monotoni.

Una range query è poco probabile che punti ad un unico shard.

I documenti sono distribuiti uniformemente




Slide22

Ranged sharding

I dati sono divisi in base ad un range di valori della chiave shard

Ogni chunk è assegnato ad un serie di valori, mongo può indirizzare le operazioni quindi solamente agli shard che hanno quella chiave.

L'efficenza dipende dalla chiave.

Range-base sharding favorisce l'efficenza delle query che si risolvono processando solamente uno shard




Una collezzione che usa x come shared key, che continua a far crescere gli elementi nello shard C




Per risolvere il problema basta usare una funzione di hash che distribuisce i dati tra i vari shard




Zones - tag aware sharding

Permette dei ritocchi sulla distribuzione dei documenti tra i vari shard

Mongo permette di creare zone e determinare in quale shard quali documenti devono essere presenti

Una volta associati i documenti alle zone, un job di mongo si occuperà di migrare i dati

Le zone migliorano la vicinanza dei dati che sono magari sparsi tra vari datacenter




Usa dei campi della shardkey quando devi definire una nuovo range di valori per la zona da coprire

Le zone si usano solitamente:

  • Isolare uno specifico sottoinsieme di dati
  • Assicurarsi che i dati importanti per quella regione risiedono in quello specifico shard
  • Rindirizzare i dati anche in base all'hardware di quello specifico shard

Balancer

è un processo che monitora il numero dei chunks presenti in ogni shark e migra i dati in modo da garantire una distribuzione equa degli stessi

Viene fatto partire quando il numero di chunk tra il più grande e il più piccolo supera una certa soglia

Viene fatto partire anche quando si aggiunge un nuovo nodo




Sharding è tipicamente combinato con la replicazione, ciò permette di avere availability e scalability

I dati sono replicati attraverso i vari nodi in replica

A replica set è definito come un gruppo di processi di mongoDB che mantengono gli stessi dati

Una replica set contiene un primary node e dei nodi secondari




Il nodo primario accetta tutte le scritture, che sono poi propagate asincronamente ai nodi secondari.

Oplog è una collezione (con un limite max) che tiene traccia di tutti i record modificati.

Il nodo primario continua ad applicare le modifiche e aggiornare l'Oplog

I nodi secondari replicano poi le operazioni sui loro dataset




Sincronizzare i dati

I nodi secondari possono sincronizzare o replicare i dati

Ci sono 2 modi per sincronizzare i dati

  • Sincronizzazione iniziale per popolare i nuovi nodi
  • Replicazione avviene in continuazione

Elezioni nel Replica set

Un replica set può avere solamente un primario, e di solito si usa una elezione per determinarlo

Le elezioni sono scatenate da:

  • Un nuovo nodo che si aggiunge
  • Quando creo il repliaca set
  • Se il secondario perde connettività con il primario (non sente più l'heartbeat)

Se ad esempio il primario è irraggiungibile oltre una determinata soglia , uno dei nodi secondari chiama un elezione per ripristinare l'operativita'




Durante l'elezione del primario:

  • Non vengono processate operazioni di scrittura
  • Vengono processate query di lettura

Un nodo primario può scoprire di essere rimasto isolato, può quindi diventare secondario

Se un nodo che può comunicare con la maggior parte dei nodi può tenere una nuova elezione

Arbitri

In caso di un numero pari di attori, gli albitri permettono di ottenere un voto a maggioranza.

Un arbitro

  • Non salva nessun dato
  • Risponde al ping ed alla richiesta di elezioni
  • Non ha necessita di avere un hardware dedicato

Letture e scritture

Nei replica set si può configurare il livello di "conferme" necessarie per le operazioni di scrittura e specificare dove le richieste di lettura devono essere indirizzate

Scrittura

Il write concerns è il numero di conferme che un operazione di write deve avere per essere considerata un completata con successo

  • 1 solo il primario è necessario
  • maggioranza la maggioranza degli attori deve rispodere positivamente



Lettura

Di solito le letture sono indirizzate al primario, volendo si può però farle fare da un nodo secondario




Le possibili preferenze di lettura:

  • primary = operations read from the primary of the replica set
  • primaryPreferred = operations read from the primary, but if unavailable, operations read from secondary members
  • secondary = operations read from the secondary members
  • secondaryPreferred = operations read from secondary members, but if none is available, operations read from the primary
  • nearest= operations read from the nearest member (= shortest ping time) of the replica set