redis – nosql repository

Nell’ articolato panorama dei database nosql riveste un certo interesse Redis. Questo database ancora immaturo per gli aspetti di scalabilità e di alta affidabilità è sicuramente interessante per le prestazioni.

Redis, generalmente indicato come respository chiave/valore, è in realtà in grado di gestire 5 tipi di dato differente ognuno adatto ad ottimizzare le prestazioni di specifiche operazioni.  Redis fa infatti della velocità il suo punto di forza.

In Redis possono essere immagazzinati:

  • stringhe
  • hash
  • liste
  • set
  • sorted set

Redis dispone anche della possibilità di scrivere delle stored procedure in LUA.

Questo database lavora mantenendo tutti i dati in memoria ma al tempo stesso è in grado di garantire la persistenza dei dati attraverso due meccanismi, entrambi disattivabili:

  • il salvataggio periodico del dataset su disco triggerabile sia su base temporale sia sulla base del numero di modifiche
  • la produzione di log delle modifiche

Redis dispone inoltre di un sistema di replica che permette di generare copie di un repository a caldo. Ci sono poi altri tool legati alla clusterizzazione ma che al momento attuale non sono in verisone stabile.

Per un’istallazione di test la via più comoda è come sempre ricorrere ai pacchetti della propria distribuzione:

aptitude install redis-server

Su debian è opportuno ricorrere ai pacchetti di backport visto che come tutti i sistemi nosql, Redis è un prodotto piuttosto giovane e ci sono spessp importanti fix nelle nuove versioni.

Sul manuale del prodotto sono riportate due considerazioni di cui è meglio tener conto:

  • è bene che il server disponga di molto swap perché in alcune situazioni Redis arriva anche a raddoppiare l’uso della memoria e nel caso questa finisca il kernel va in protezione ed uccide il processo.
  • Redis ha problemi con l’uso standard di Linux della memoria virtuale; viene quindi consigliato di impostare ad uno vm.overcommit_memory

Redis può essere fatto lavorare in una modalità analoga a quella di una cache impnendogli di sovrascrivere i TTl degli oggetti se si presenta la necessità di liberare memoria.

Con il pacchetto viene istallato anche un client

redis-benchmark
redis-cli

Il primo permette una prima verifica del funzionamento del sistema e da  un’idea delle prestazioni del sistema. Il secondo è un client che permette di interrogarlo.
Ovviamente l’accesso più naturale ad un sistema di questo tipo sono le librerie di qualche linguaggio.

Ad ogni modo una volta dentro si possono ottenere informazioni sullo stato del daemon con il comando info:

$>redis-cli
redis 127.0.0.1:6379> info

oppure si possono gestire i dati con i molti comandi possibili:

redis 127.0.0.1:6379> set nome:pippo '{"città":  topolinia, "razza": cane}'
redis 127.0.0.1:6379> get nome:pippo

get e set sono alcuni dei comandi relativi alle stringe. Per la gestione di altri tipi di dati ci sono i comandi relativi.

 

MongoDB replicaset test drive

Segnalo un’interessante iniziativa realizzata dal team di MongoDB con il supporto di amazon:

Accedendo a  test drive è possibile accedere ad un’istallazione di test di mongodb su cui provare il sistema di replica e testarne l’ottimizzazione.

Memcached – 2

Come già accennato nel precedente post, memcached è un daemon piuttosto semplice da configurare e istallare.

Sui sistemi Debian e derivati memcached può essere istallato con

sudo aptitude install memcached

altrimenti si può ricorrere ai sistemi di pacchettizzazione delle altre architetture o direttamente ai sorgenti rilasciati dagli sviluppatori.

Se si istalla da pacchetto debian a valle dell’istallazione verrà lanciato un daemon che, come risulta da ps aux, sarà:

/usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1

La configurazione completa sarà invece in

/etc/memcached.conf

Analizziamo i parametri di avvio

  • -l: l’ip su cui il demone è in ascolto
  • -p: la porta TCP su cui il demone è in ascolto
  • -m: la quantità di memoria (in MB) che viene assegnata al demone memcached

e non visibili perché con impostazioni di default (ma riportati sul file di configurazione)

  • -d: memcached viene lanciato in modalità demone e quindi rilascia il controllo all’ambiente che lo ha avviato
  • logfile: path del file di log
  • -c: numero massimo di connessioni contemporanee accettate
  • -M: ritorna errore nel caso non ci sia memoria disponibile
  • -v: incremento del livello di dettaglio nei log
  • -vv: ulteriore incremento del livello di dettaglio nei log

I più imprtanti per il tooning sono ovviamente -m, -c e -M. Nel determinare il primo bisogna tener conto che se l’uso di memcached fa si che il server debba appoggiarsi sullo swap l’effetto complessivo sarà con ogni probabilità quello di peggiorare le prestazioni.

Vale la pena spendere due parole su -M; di default memcached quando non ha più spazio disponibile sovrascrive i valori più vecchi e meno utilizzati. Questo viene indicato come evictions nei report e nella documentazione. Se questo sia accettabile o no dipende da considerazione sull’applicazione che si sta realizzando ma è in genere molto ragionevole per della cache. -M permette di cambiare questo comportamento nel caso dovesse essere necessario.

Per connettersi ad un server memcached è possibile utilizzare un semplice telnet:

telnet localhost 11211

Una volta connessi si possono in teoria eseguire tutte le operazioni; ad esempio salvare una variabile

set pluto 123 0 3
o

dove viene detto di salvare nella variabile pluto il valore o. 123 è un numero arbitrario usato nel protocollo di comunicazione, 0 il tempo di expire del valore (in questo caso infinito) e 3 la dimensione dei dati che verranno inviati.

Si può poi rileggere lo stesso valore con un

get pluto

Attenzione che anche un solo spazio in più fa fallire l’esecuzione del comando.

Questo uso con telnet è paragonabile all’invio della posta con lo stesso strumento ma rimane utile per test e troubleshooting. Qualsiasi uso applicativo deve passare attraverso un’apposita libreria in un qualche linguaggio (php, perl…). Oltretutto a questa libreria sono completamente demandate le funzionalità necessarie per l’uso in cluster.

Il telnet inoltre permette di accedere a ben quattro report molto dettagliati sullo stato interno del daemon:

stats
stats items
stats slabs
stats sizes

Questi quattro comandi sono alla base di report più ad alto livello. Decisamente più maneggevole è ad esempio memcache-top. Questo permette di avere un’istantanea molto comprensibile  dello stato di un pool e dell’efficacia dell’uso che se ne fa:

memcache-top --commands --instance=serv1:port1[,serv2:port2...]

il report sarà qualche cosa come:

memcache-top v0.5       (default port: 11211, color: on, refresh: 3 seconds)

INSTANCE                USAGE   HIT %   CONN    TIME    EVICT   READ    WRITE
serv1:port1             90%     80%     800     0.6ms   20      10G     20G    
serv2:port2             90%     80%     700     0.5ms   10      15G     30G  
...

AVERAGE:                90%     80%     750     0.5ms   15      12.5G   25G  

TOTAL:                  2GB            1500     11.0ms  30      25G     50G    
(ctrl-c to quit.)

Questo report, se non specificato diversamente, viene rigenerato ogni 3s e si riferisce a questo intervallo temporale.

Memcached – 1

Sui sistemi che devono fornire contenuti web dinamici la cache diventa rapidamente un aspetto cruciale al crescere del traffico; su web infatti si possono raggiungere frequenze di accessi ai dati veramente notevoli. Per fortuna, anche se questo è sempre meno vero, c’è una fortissima sproporzione tra gli accessi in lettura e quelli in scrittura e questo permette notevoli semplificazioni nelle soluzione di questo problema.

Di fatto il modo migliore per gestire la massa degli accessi in lettura è l’utilizzo di un qualche sistema di cache. Cache che dovrà essere quanto possibile veloce e semplice. Memcached nasce proprio per questo scopo: permette di riservare su un server un’area di memoria, renderla accessibile via TCP e di salvare su memoria dei valori recuperabili successivamente sulla base di una chiave.

Per rimanere semplice e veloce, memcached mantiene i dati solo in memoria e non li scrive su disco, ne consegue che ogni volta che viene riavviato perde tutti i propri dati. Questo però è un compromesso assolutamente ragionevole per della cache.

Supponiamo ora di dover gestire un sito ad alto traffico e di renderci conto che il nostro database sta divenendo un collo di bottiglia per il nostro sistema. Decidiamo quindi di introdurre memcached e di riuscire in questo modo sia a migliorare le prestazioni del sito sia a ridurre notevolmete gli accessi al database. Nei mesi successivi il traffico sul nostro sito potrà quindi crescere ancora. Si è venuto a creare in questo modo un problema piuttosto insidioso: se il daemon memcached si dovesse riavviare, perderemmo la nostra cache e ci troveremmo con un database non più sufficiente a supportare il nostro sito: sarebbe quindi molto difficile far ripartire il sistema nel suo insieme.

In pratica, se si vogliono sfruttare veramente i vantaggi di memcached bisogna distribuire la cache su diversi server (tutti i server apache ad esempio) in modo tale che al riavvio di un demone memcached si perderebbe solo una parte della cache e il sovraccarico sui database sarebbe quindi contenuto.

I cluster di server memcached sono peculiari in quanto ogni server è un’ entità a se che si preoccupa solamente di immagazzinare e restituire i dati dalla propria memoria e non ha conoscenza del fatto di essere parte di un pool. La logica della gestione del pool è demandata completamente ai client.

Ogni client suddivide la cache in tante parti sulla base della conoscenza deila lista dei server del pool e si andrà a poi a connettere sul server corretto per il salvataggio o il recupero dei dati. L’algoritmo utilizzato per questa operazione è piuttosto interessante e sarà argomento di un altro post.

Un altro problema che si può presentare con questi sistemi deriva dal fatto che su molti siti web ci sono pagine molto accedute e pagine a basso traffico: su un portale probabilmente gran parte degli accessi saranno sulla homepage. Oggetti della cache che vengono utilizzati in queste pagine saranno quindi particolarmente critici e la perdita di uno di questi potrebbe essere un problema anche se percentualmente gran parte della cache è comunque disponibile.

Come detto gà più volte il daemon è piuttosto semplice e semplice è anche la sua configurazioni. La configurazione si riduce di fatto a scegliere:

    • la memoria da riservare
    • il numero massimo di connessioni accettabili
    • se salvare dei log su file

How To – ambiente di test per mongodb – uso Sharding

Vedremo qui alcune operazioni per la gestione dello sharding in mongodb. Utilizzeremo l’ambiente di test utilizzato nell’articolo precedente.

Vedremo come aggiungere e togliere nodi allo sharding e come attivare lo sharding ai db e alle collections.

Per prima cosa verifichiamo che tutti i demoni sono presenti;
ps aux|grep mongo
dovrebbe restituire tre config server, 1 control server e 6 data server.

Connettiamoci al control server e di li entriamo nel db admin per vedere gli elementi dello sharding

mongo --port 27021
use admin
db.runCommand( {listshards:1} )

Si otterrà qualche cosa come

mongos> db.runCommand( {listshards:1} )
{
"shards" : [
{
"_id" : "rs1",
"host" : "rs1/localhost:27101,localhost:27102"
},
{
"_id" : "rs2",
"host" : "rs2/localhost:27201,localhost:27202"
}
],
"ok" : 1
}

E’ possibile rimuovere un elemento dello sharding

db.runCommand( {removeShard: "rs1/localhost:27101,localhost:27102"} )

L’operazione richiede del tempo perché comporta la ridistribuzione dei dati sugli altri shards. Il comando

db.printShardingStatus()

o

sh.status()

mostra lo stato dello sharding e quindi il progresso di questa operazione.
Altre informazioni si possono avere ripetendo il comando di rimozione.

Per aggiungere un replicaset allo sharding

db.runCommand( {addShard: "rs1/localhost:27101"} )

E’ il caso di sottolineare che tutto questo prepara solamente l’infrastruttura dello sharding che, essendo intimamente legato alla struttura dei dati richiede per essere attivato realmente degli interventi a livello di collection e database.

Procediamo quindi con la creazione di un set minimale di dati.
Per prima cosa ci si connette al control server:

mongo --port 27021

Creiamo quindi il nuovo db e verifichiamo

use mydb
db

Creiamo due documenti

a = { name : "pippo", city : "topolinia" }
b = { name : "paperino", city : "paperinia" }

e, infine, inseriamoli in una collection

db.personaggi.insert( a )
db.personaggi.insert( b )

Verificiamo quindi quanto è stato fatto

show collections
db.personaggi.find()

Passiamo allo sharding vero e proprio. Abilitiamo lo sharding sul database

sh.enableSharding( "mydb" )

e sulla collection

db.personaggi.ensureIndex( { "city": 1 } )
sh.shardCollection("mydb.personaggi", { "city": 1 } )

Il primo comando crea un indice sul campo city. Questo indice è necessario per poter fare lo sharding.
Ora il comando sh.status() mostrerà la lista e la posizione dei chunk (partizioni) della collection di cui è stato effettuato lo sharding.
Essendo pochissimi i documenti si troveranno tutti sullo stesso nodo. Il chunk ha infatti di default una dimensione di 64MB riducibile fino a 1MB; possiamo però divertirci a spostare sull’altro shard il chunk contenente la city topolinia:

db.adminCommand( { moveChunk : "mydb.personaggi", find : {city : "topolinia"}, to : "rs2" } )

e verificare il risultato con

sh.status()

La scelta della chiave dello sharding e in misura minore della dimensione dei chunk sono essenziali per determinare le prestazioni del cluster ma è argomento che va oltre lo scopo di questo post.

How To – ambiente di test per mongodb – uso ReplicaSet

Vedremo qui alcune operazioni per la gestione del ReplicaSet. Utilizzeremo l’ambiente di test utilizzato nell’articolo precedente.

In particolare vedremo come effettuare il takeover di un ReplicaSet, e come aggiungere o togliere un nodo dal cluster.

Per prima cosa verifichiamo che tutti i demoni sono presenti;
ps aux|grep mongo
dovrebbe restituire qualche cosa come

mongodb 5570 0.4 1.2 261884 46924 ? Sl 09:48 0:14 /usr/bin/mongod --port 27001 --dbpath /var/lib/mongodb/cfg1 --configsvr --fork --rest --logpath /var/log/mongodb/cfg1.log
mongodb 5627 0.4 1.2 261884 46984 ? Sl 09:58 0:10 /usr/bin/mongod --port 27002 --dbpath /var/lib/mongodb/cfg2 --configsvr --fork --rest --logpath /var/log/mongodb/cfg2.log
mongodb 5642 0.4 1.2 261896 46964 ? Sl 09:58 0:10 /usr/bin/mongod --port 27003 --dbpath /var/lib/mongodb/cfg3 --configsvr --fork --rest --logpath /var/log/mongodb/cfg3.log
mongodb 5659 0.3 0.0 108680 3632 ? Sl 09:59 0:09 /usr/bin/mongos --port 27021 --configdb localhost:27001,localhost:27002,localhost:27003 --fork --logpath /var/log/mongodb/ctrl.log
mongodb 5696 0.4 0.7 2671608 31244 ? Sl 09:59 0:11 /usr/bin/mongod --port 27101 --dbpath /var/lib/mongodb/rs11 --replSet rs1 --shardsvr --rest --fork --logpath /var/log/mongodb/rs11.log
mongodb 5757 0.4 1.2 630700 47208 ? Sl 09:59 0:10 /usr/bin/mongod --port 27103 --dbpath /var/lib/mongodb/rs1a --replSet rs1 --rest --fork --logpath /var/log/mongodb/rs1a.log
mongodb 6645 0.5 0.8 3049440 32212 ? Sl 10:03 0:13 /usr/bin/mongod --port 27102 --dbpath /var/lib/mongodb/rs12 --replSet rs1 --shardsvr --rest --fork --logpath /var/log/mongodb/rs12.log
mongodb 7103 0.4 1.2 4783104 47512 ? Sl 10:08 0:09 /usr/bin/mongod --port 27201 --dbpath /var/lib/mongodb/rs21/ --replSet rs2 --shardsvr --rest --fork --logpath /var/log/mongodb/rs21.log
mongodb 7194 0.4 1.2 3016636 47316 ? Sl 10:08 0:08 /usr/bin/mongod --port 27202 --dbpath /var/lib/mongodb/rs22/ --replSet rs2 --shardsvr --rest --fork --logpath /var/log/mongodb/rs22.log
mongodb 7399 0.4 1.2 5093292 47060 ? Sl 10:09 0:08 /usr/bin/mongod --port 27203 --dbpath /var/lib/mongodb/rs2a --replSet rs2 --rest --fork --logpath /var/log/mongodb/rs2a.log

ovvero tre config server, 1 control server e 6 data server.

Come passo successivo verifichiamo lo stato dei ReplicaSet. Per farlo ci connettiamo al nodo primary di ogni replicaset, eventualmendo provando i vari nodi fino a trovare quello che si presenta come primary al prompt. Una volta dentro si richiede la configurazione attuale e lo stato della replica. In comandi

mongo --port 27101
rs.config()
rs.status()

I risultati di questi comandi sono abbastanza parlanti.

E’ possibile cambiare il nodo primario. Se tutti i nodi sono presenti si può dire al sistema di farlo, se il nodo primario dovesse morire invece il processo sarebbe automatico.
Trattandosi di un’ambiente di test simuliamo la morte del nodo primario con un kill e osserviamo il takeover del cluster. Utilizzando nel kill il PID ricavato in precedenza:

kill

non essendoci attività in corso nel cluster il takeover sarà quasi immediato; attenzione che in un sistema di produzione sotto carico può essere necessario del tempo.
Se ci si connette quindi al nuovo Primary e si osserva lo stato del cluster:

mongo --port 27102
rs.status()

si vedrà che il vecchio nodo primary risulta ora non raggiungibile.
Se lo riavviamo

sudo -u mongodb /usr/bin/mongod --port 27101 --dbpath /var/lib/mongodb/rs11 --replSet rs1 --shardsvr --rest --fork --logpath /var/log/mongodb/rs11.log

il nodo rientrerà come secondary. Se nel frattempo fossero intervenute modifiche ai dati il nodo appena reinserito riallineerebbe i propri dati prima di rientrare realmente in gioco. Nel caso fosse passato troppo tempo e non fossero più presenti i passi incrementali tutti i dati verrebberio ritrasferiti al secondary.
Vediamo ora come Spostare il primario su di un’altro nodo. Per farlo bisogna, lavorando sul nodo primary, recuperare la configurazione attuale.

mongo --port 27102

da prompt

cfg = rs.config()

assegnare diverse priorità ai nodi

cfg.members[0].priority = 1
cfg.members[1].priority = 0.5

attivare la nuova configurazione

rs.reconfig(cfg)

Dopo qualche secondo il nodo a cui si è connessi diventerà secondario e l’altro primario.

E’ infine possibile togliere e aggiungere nodi da un replicaset. E’ sempre preferibile effettuare queste operazioni con i nodi coinvolti attivi.
Iniziamo rimuovendo il nodo secondary.
Entriamo nel nodo primary e rimuoviamo il nodo secondary:
mongo --port 27101
rs.remove("localhost:27102")

la configurazione riporterà le modifiche e se ci si connette al nodo rimosso non risulterà più secondary al prompt.
Ripristiniamo ora il nodo
mongo --port 27101
rs.add("localhost:27102")

Non essendoci molti dati da aggiornare il nodo risulterà presto come secondary.

How To – ambiente di test per mongodb – infrastruttura

Visto che abbiamo parlato di mongodb, affrontiamo la realizzazione di un piccolo ambiente di test che ci permetta di testare tutte le funzionalità del prodotto.

Questo articolo vuole essere solo una quida pratica, per maggiori dettagli sul prodotto si può fare riferimento al mio articolo o al sito ufficiale del prodotto.

Vogliamo realizzare un sistema che possa effettuare sia sharding sia replica dei dati. Trattandosi di un ambiente di test avvieremo tutti i demoni coinvolti sullo stesso server. La configurazione finale sarà:

  1. 3 config server
  2. 1 control server
  3. 6 demoni di dati

questi ultimi saranno organizzati in 2 ReplicaSet ognuno contenente 1 Primary server, 1 secondary server ed un arbiter.

Il primo passo è ovviamente istallare il prodotto. Si può scaricare l’ultima versione dal sito del produttore o utilizzare il gestore di pacchetti della propria distribuzione.

Se si utilizza un sistema debian o derivato bisognerà eseguire come utente root:
aptitude update
aptitude install mongodb

Se si è utilizzato  Ubuntu 13.04 si avrà mongo nella versione 2.2.4.

Almeno su debian e derivati quello che viene istallato dal gestore di pacchetti è un demone autonomo: non essendo quello che interessa a noi, procederemo fermandolo e modificando l’ambiente in modo da ospitare la nostra architettura.

Da qui conviene lavorare come utente root.
ps aux|grep mongodb ci dirà se ci sono demoni da interrompere. Eventualmente procederemo con un kill al loro spegnimento.

Dobbiamo poi assicurarci che il demone non riparta al riavvio della macchina:
update-rc.d -f mongodb remove

Assicuriamoci dell’esistenza dell’ utente mongodb e del gruppo omonimo con cui far girare i demoni mongodb:
grep mongo /etc/user
che restituirà qualche cosa come
mongodb:x:121:65534::/home/mongodb:/bin/false
e se non presente creiamolo
Analogamente per il gruppo
grep mongo /etc/group
che restituirà qualche cosa come
mongodb:x:133:mongodb
e se non presente creiamolo.

Creiamo delle directory per accogliere i dati e i log
mkdir /var/lib/mongodb/
mkdir /var/lib/mongodb/cfg1
mkdir /var/lib/mongodb/cfg2
mkdir /var/lib/mongodb/cfg3
mkdir /var/lib/mongodb/ctrl
mkdir /var/lib/mongodb/rs11
mkdir /var/lib/mongodb/rs12
mkdir /var/lib/mongodb/rs1a
mkdir /var/lib/mongodb/rs21
mkdir /var/lib/mongodb/rs22
mkdir /var/lib/mongodb/rs2a
mkdir /var/log/mongodb/

e assegnamogli utenza e gruppo
chown -R mongodb:mongodb /var/lib/mongodb/
chown -R mongodb:mongodb /var/log/mongodb/

Avviamo ora i config servers:

sudo -u mongodb /usr/bin/mongod --port 27001 --dbpath /var/lib/mongodb/cfg1 --configsvr --fork --rest --logpath /var/log/mongodb/cfg1.log
sudo -u mongodb /usr/bin/mongod --port 27002 --dbpath /var/lib/mongodb/cfg2 --configsvr --fork --rest --logpath /var/log/mongodb/cfg2.log
sudo -u mongodb /usr/bin/mongod --port 27003 --dbpath /var/lib/mongodb/cfg3 --configsvr --fork --rest --logpath /var/log/mongodb/cfg3.log

il control server
sudo -u mongodb /usr/bin/mongos --port 27021 --configdb localhost:27001,localhost:27002,localhost:27003 --fork --logpath /var/log/mongodb/ctrl.log
I nodi dati

sudo -u mongodb /usr/bin/mongod --port 27101 --dbpath /var/lib/mongodb/rs11 --replSet rs1 --shardsvr --rest --fork --logpath /var/log/mongodb/rs11.log
sudo -u mongodb /usr/bin/mongod --port 27102 --dbpath /var/lib/mongodb/rs12 --replSet rs1 --shardsvr --rest --fork --logpath /var/log/mongodb/rs12.log
sudo -u mongodb /usr/bin/mongod --port 27103 --dbpath /var/lib/mongodb/rs1a --replSet rs1 --rest --fork --logpath /var/log/mongodb/rs1a.log
sudo -u mongodb /usr/bin/mongod --port 27201 --dbpath /var/lib/mongodb/rs21 --replSet rs2 --shardsvr --rest --fork --logpath /var/log/mongodb/rs21.log
sudo -u mongodb /usr/bin/mongod --port 27202 --dbpath /var/lib/mongodb/rs22 --replSet rs2 --shardsvr --rest --fork --logpath /var/log/mongodb/rs22.log
sudo -u mongodb /usr/bin/mongod --port 27203 --dbpath /var/lib/mongodb/rs2a --replSet rs2 --rest --fork --logpath /var/log/mongodb/rs2a.log

Attenzione che ogni nodo dati prealloca ~3GB di spazio disco.
Il passo successivo è quello di creare i ReplicaSet. I ReplicaSet si controllano dal nodo primary degli stessi; connettiamoci quindi ad uno dei nodi
mongo --port 27101
dalla linea dicomando
conf = { _id: "rs1", members: [ { _id: 0, host: "localhost:27101" } ] }
rs.initiate( conf )

si può poi visualizzare lo stato della configurazione
rs.conf()
il prompt dovrebbe a questo punto riportare la dicitura “PRIMARY”.
Aggiungiamo ora gli altri due nodi al ReplicaSet

rs.add("localhost:27102")
rs.addArb("localhost:27103")

si può visualizzare lo stato del replicaSet con:
rs.status()

Lo stesso andrà fatto per il il secondo ReplicaSet
mongo --port 27201
dalla linea dicomando
conf = { _id: "rs1", members: [ { _id: 0, host: "localhost:27201" } ] }
rs.initiate( conf )

si può poi visualizzare lo stato della configurazione
rs.conf()
il prompt dovrebbe a questo punto riportare la dicitura “PRIMARY”.
Aggiungiamo ora gli altri due nodi al ReplicaSet

rs.add("localhost:27202")
rs.addArb("localhost:27203")

si può visualizzare lo stato del replicaSet con:
rs.status()

Passiamo infine alla preparazione dello sharding. Si lavora ora connessi al control server
mongo --port 27021
e si seleziona il database admin
use admin
Aggiungiamo poi i replicaset come elementi dello sharding
db.runCommand( { addshard: "rs1/localhost:27101"} )
db.runCommand( { addshard: "rs2/localhost:27201"} )

Nell’ultimo comando è presente il nome del replicaset; questo permette al sistema di aggiungere automaticamente tutti i nodi del ReplicaSet e di spostare le attività sul nodo secondary che diventa primary in caso di fault.

Per utilizzare realmente lo sharding bisogna poi configurare i singoli database.

Vedremo in un altro post più in dettaglio l’utilizzo del sistema

mongodb introduction

MongoDB , come del resto tutti gli strumenti di questo tipo, ha una struttura pensata per massimizzare scalabilità e prestazioni in assenza di single points of failur;  quest’ultima  è fondamentale per avere un sistema che permetta di massimizzare la continuità del servizio. Come approfondiremo più avanti, per avvicinarsi il più possibile ai suddetti obiettivi, il sistema rinuncia a garantire l’ACID compliance, in particolare non garantisce che un dato, di cui sia stata data anche conferma di ricezione, venga realmente acquisito dal sistema. Questo ovviamente accade in situazioni normali ma può non accadere per esempio in caso di fault di uno dei nodi. Se questo sia accettabile o meno, dipende dall’applicazione che si sta realizzando.

I dati su MongoDB sono quasi completamente destrutturati, o meglio la struttura in ogni elemento minimo (documento) è di fatto arbitraria e non legata rigidamente aa quella degli altri documenti. I dati sono organizzati in documenti di contenuto arbitrario, i documenti in collection e le collection in database. I vari documenti di una data collection possono avere contenuti strutturati in modo differente. Ad esempio si potrebbe pensare ad un documento contenente le informazioni relative all’utente di un’applicazione social. Questi utenti potrebbero essere raggruppati in una collection ed avranno una parte di informazioni comune a tutti ed una parte presente solo su alcune utenze. La possibilità di definire indici velocizza poi l’accesso alle informazioni.

I dati sono acceduti tramite json e immgazzinati tramite bson.

MongoDB fa largo uso della memoria ed è ovviamente particolarmente efficente se la memoria disponibile è sufficiente a contenere tutti i dati che vengono acceduti spesso. In un certo senso può essere considerato un unteressante sostituto di sistemi di cache quali memcache; rispetto a quest’ultimo ad esempio, a costo di un overhead soprattutto in scrittura, potrebbe gestire meglio sitauzioni quali un riavvio in assenza di cache.

Quello che però rende MongoDB veramente interessante sono due caratteristiche: la replica e lo sharding dei dati.  Caratteristiche combinabili a patto di avere a disposizione un numero di server sufficiente (non meno di tre).

Esaminiamole prima separatamente per poi affrontare la questione della loro combinazione.

La replica permette di avere gli stessi dati su due o più nodi in modo tale da poter mantenere attivo il servizio anche in caso di fault di uno dei server. In questo modo si riesce ad ottenere un sistema in alta affidabilità. Dato che per ogni gruppo di nodi (denominato ReplicaSet) uno solo è in realtà accessibile ai client (primary node), aumentare il numero di nodi ha il solo scopo di diminuire la probabilità di un’interruzione del servizio.

E’ qui opportuno entrare un poco più in dettaglio sulla replicazione dei dati: questa avviene in modo asincrono per avere dei tempi di risposta accettabili dal sistema. In pratica i client mongodb considerano completata la scrittura quando il nodo primary notifica l’avvenuta scrittura. In questa fase però i nodi secondary non necessariamente si sono aggiornati. Se quindi il primary node dovesse fallire in questo situazione verrebbe sostituito da un secondary node non completamente aggiornato e l’informazione persa.

In un ReplicaSet il nodo primary viene scelto attraverso un processo di elezione che, per ragioni interne al protocollo, richiede un numero dispari di nodi votanti; non è quindi possibile avere un ReplicaSet composto da un numero pari piccolo di nodi (2 in particolare). Per fortuna è possibile aggiungere al pool un nodo speciale, denominato arbitro, che interviene solamente nella fase di scelta del nodo primario minimizzando l’uso di risorse. Questo nodo può quindi essere collocato su server che svolgono altre funzioni senza un impatto significativo.

L’altra caratteristica fondamentale di MongoDB è la possibilità di effettuare lo sharding dei dati; ovvero di distribuire documenti di una data collection  (dati omogenei) su più server nascondendo poi questa complessità al client. Nell’esempio di prima  posso far si che gli utenti italiani, francesi e tedeschi vengano gestiti da server differenti delegando a mongoDB la gestione di questo aspetto a patto di avere un campo country in ogno documento utente. Questa caratteristica è fondamentale in quanto permette di scalare orizzontalmente un cluster mongodb permettendo una crescita morbida delle prestazioni del sistema e soprattutto dei costi.

La replica permette di ottenere sistemi in alta affidabilità e lo sharding sistemi scalabili. Probabilmente però si vorranno entrambe le caratteristiche.  Per fortuna MongoDB permette di combinare le due caratteristiche utilizzando come nodi dello sharding degli interi ReplicaSet.

Lo sharding è una caratteristica piuttosto complessa e per avere un sistema in sharding bisogna introdurre nel nostro cluster MongoDB ben tre tipi di demoni differenti: per la verità lo stesso demone con tre ruoli differenti. Avremo bisogno di  demoni che mantengano la configurazione dello sharding (config server), demoni che mantengano i dati applicativi e demoni che gestiscano le connessioni dei client facendosi carico di suddividere le query tra i nodi dati coinvolti, raggruppare i risultati e restituirli ai client.

La minima configurazione avviabile in  sharding anche per test prevede quindi:

  1. esattamente 3 nodi config server. Non c’è possibilità di scelta.  Devono essere diversi per evitare unique point of failure. E’ interessante notare che con soli due nodi di queto tipo attivi, ad esempio in seguito ad un fault, il sistema non accetta modifiche alla configurazione dello sharding ed è per questo aspetto in sola lettura pur rimanendo perfettamente funzionante per il resto.
  2. almeno un nodo control server
  3. almeno un nodo dati. La minima configurazione interessante, anche per test è però, secondo me,  di sei nodi (due ReplicaSet ognuno con un primary, un secondary e un arbiter) altrimenti lo sharding è degenere.

Una configurazione minimale di produzione che sia sia ridondata sia scalabile si può quindi ottenere su tre server: attenzione però nel dimensionamento del sistema perché alcune operazioni su quasto sistema, legate soprattutto alla modifica dello sharding, possono comportare livelli di carico ben oltre il normale.

Vedremo altrove come effettuare il setup di un ambiente di test completo.

Prima di utilizzare il prodotto in un ambiente di produzione è comunque il caso di studiarlo in modo più approfondito su http://www.mongodb.org.

database noSQL

Negli ultimi anni, sulla spinta delle nuove problematiche poste dai servizi social o cloud su Internet, sono diventati di gran moda i così detti database NoSQL.

Sotto questa denominazione vengono fatte rientrare tecnologie e prodotti molto diversi tra loro ma che sono accomunati dalla ricerca di soluzioni migliori di quelle ottenibili con database relazionali al problema della scalabilità. Tipicamente nelle applicazioni internet ci si trova a dover gestire basi di dati di cui è difficile controllare la crescita, pur dovendo mantenere tempi di risposta brevi.

Dato che non si riesce ad ottenere un sistema che sia al tempo stesso veloce, affidabile e scalabile i database NoSql ammorbidiscono in genere il requisito dell’affidabilità, estremamente stringente nel caso dei database relazionali e, per potre ottenere prestazioni migliori ed una maggiore scalabilità.