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.

 

mysql e binlog

Una delle caratteristiche più interessanti di MySQL è il suo sistema di replica. Questo lo rende possibili molte configurazioni che rendon il prodotto estremamente flessibile.

Si può abilitare MySQL per replicare tutte le query che comportano delle modifiche alla base dati su degli appositi file di log detti binlog. Questo, con le nuove versioni di MySQL può avvenire in due modi: riportando le query o riportando le modifiche ai file. Non approfondiremo oltre, in questo post, le differenze tra queste due modalità e faremo riferimento al caso in cui vengano riportate le query. Gran parte delle considerazioni rimangono valide in entrambi i casi.

I binlog servono almeno in 3 situazioni molto critiche:

  • assieme ai backup per la Point in Time Recovery: se al momento del backup si sono salvati valori riportati da “show master status\G”, è possibile riapplicare tutte le modifiche effettuate al database e riporatte sui binary log fino ad un preciso istante. Idealmente ricosytruendo lo sato del database subito prima del problema.
  • sistemi in replica: i binlog solo lo strumento attraverso il quale i vari nodi in replica si comunicano le rispettive modifiche alla base dati.
  • analisi a posteriori di bug o attacchi

Si noti, avendone la possibilità, è opprtuno valutare se farli scrivere su dischi e attraverso un controller diverso da quelli utilizzati per i file di dati, trattandosi di una scrittura potenzialmente pesante.

La generazione dei bynary log su MySQL non è abilitata di default ma bisogna aggiungere un gruppo di statement di configurazione come il seguente prima delle configurazione dei vari Engine :

server-id = 1
log_bin = /data/log/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 500M
log_slave_updates = 1
#binlog_do_db = include_database_name
#binlog_ignore_db = include_database_name

la lista dei parametri sopra non è esaustiva.

  • server-id: questo valore è fondamentale nei sistemi in replica in quanto permette al server di capire quale nodo ha ricevuto la query per primo. Bisogna quindi assegnare un numero univoco per ogni server coinvolto
  • log_bin: è il template del nome del file che viene generato. Questa linea abilita anche i binlog.
  • expire_log_days: definisce per quanti giorni i binlog vengono mantenuti da MySQL sul disco
  • max_binlog_size: definisce le dimensioni massime del file. Al raggiungimento di questo limite il file viene chiuso e ne viene aperto un’altro. Nello scegliere le dimensioni di questi file bisogna fare un compromesso tra le loro dimensioni e il loro numero per trovare la situazione più facilmente gestibile.
  • log_slave_updates: nei sistemi in replica permette di specificare se il nodo deve salvare sul binlog anche le query che ha ricevuto dal proprio master o solo eventuali query ricevute direttamente
  • binlog_do_db: se presente specifica la lista dei database le cui modifiche devono essere riportate sui binary log
  • binlog_ignore_db: se presente specifica la lista dei database le cui modifiche non devono essere riportate sui binary log

Attenzione a statement come gli ultimi due:

  • hanno comportamenti differenti nel caso si abbiano nei binary log le query o le modifiche
  • il db a cui si riferische è quello della connessione che non è necessariamente quello della query (es. select * from agenda.telefono)

E’ assolutamente opportuno un approfondimento sui manuali ufficiale prima di procedere con sistemi di produzione.

Come si può dedurre dal nome indipendentemente dal formato scelto i binlog sono file binary non facilmente gestibili con gli strumenti standard di UNIX. Viene in aiuto un tool specifico mysqlbinlog. Questo comando applicato ad una serie di file manda in standard output i comandi sql nella giusta sequenza temporale. Ha molte opzioni e, ovviamente, bisogna far riferimento al suo manuale prima di usarlo su sistemi di produzione.

Cluster Mysql

Una delle caratteristiche più interessanti di mysql è la possibilità di replicare le modifiche al database su di un altro nodo che verrà mantenuto quindi aggiornato e al tempo stesso sarà disponibile per effettuare query in lettura,  backup o altro.

Questa caratteristica da luogo alla possibilità di implementare cluster con configurazioni (topologie) più o meno articolate che, avendo caratteristiche differenti, potranno tornare utili in situazioni diverse.

Quello che si cerca di ottenere in genere con queste configurazioni è in genere ridondanza per gestire fault dei server minimizzando il disservizio, scalabilità orizzontale nelle prestazioni o entrambe. Per scalabilità orizzontale si intende la possibilità di aggiungere server al sistema per aumentarne le prestazioni e si contrappone alla scalabilità verticale in cui per far crescere le prestazioni bisogna sostituire l’hardware precedente con altro in genere molto più costoso.

Altre volte quello che si vuole è un nodo disponibile per attività non standard: la possibilità di effettuare dei backup fuori linea, un ambiente su cui generare reportistica senza impattare sulle prestazioni del sistema di produzione, un ambiente su cui fare debugging o altro.

La configurazione più semplice possibile è una configurazione master-slave. In questa configurazione un solo nodo del cluster riceve le chiamate che modificano i dati mentre l’altro è disponibile per altre attività come i backup, attività di reporting o anche l’esecuzione di query di sola lettura di dati.

Sono utili alcune precisazioni:

  • mysql implementa la replica ma nessun meccanismo di distribuzione delle query. E’ demandato all’applicativo l’onere di inviare le query al server giusto. Questo si può fare sia tramite logica interna al client, sia con strumenti che si interpongono tra client e server quali proxy, pooling o loadbalancing.
  • mysql implementa la replica ma non implementa nessun meccanismo di tackeover: se il master dovesse morire il cluster perderebbe il nodo su cui vengono eseguite le query le query in scrittura. Anche in questo caso è demandato a strumenti esterni quali script, load balancing o lo stesso applicativo client l’onere di implementare la ridondanza. C’è da dire poi che questa non è la tipologia di cluster migliore per implementare la ridondanza data la sua forte asimmetria.
  • Se si utilizza il nodo mysql slave per fare dei backup si può avere un dump coerente con strumenti standard anche solo interrompendo i processi di replica; in questo modo infatti si sono sospese tutte le scritture sul nodo in esame.
  • se si utilizza il nodo mysql slave per fare dei backup e si vuole poter sfruttare il point in time recovery, bisogna abilitare i binary log anche sul nodo slave dato che sono questi che dovranno essere associati al backup stesso. O, meglio, bisogna associare al backup l’informazione dello stato della replica rispetto ai binary log del master; informazioni disponibili sullo stato della replica.

Una topologia di replica secondo me molto più interessante e flessibile è quella master-master. In questa configurazione si ha una coppia di server mysql ognuno dei quali replica le modifiche dell’altro.

Questa configurazione è estremamente flessibile perché, data la sua simmetria, è molto facile realizzare un sistema in High Havailability in cui un nodo venga sostituito dall’altro. E’ possibile farlo con diversi struementi, ad esempio heartbeat o un bilanciatore. Inoltre si può sia sfruttare entrambi i nodi per le scritture, sia utilizzare il cluster master-master come se fosse un sistema master-slave sia sfrutatrlo solamente per la ridondanza accedendo in condizioni normali ad un solo nodo. In tutti i casi però la simmetria del sistema fa si che nell’automatizzare lo scambio dei ruoli dei server non sia necessario intervenire sulla configurazione del database.

Ovviamente questa semplicità ha un costo, o se si preferisce i problemi vengono spostati altrove. In questa configurazione ci sono molte sottigliezze legate alla replica di cui l’applicazione deve tenere conto per non dare risultati sbagliati; sottiglizze legate soprattutto agli autoincrement, agli ordinamenti e a considerazioni di ACID compliance.

Se i nodi sono più di due le possibilità sembrano moltiplicarsi ma le vie ragionevolmente percorribili non sono poi così tante.

La prima possibilità è quella di avere un master e molti slave: questa configurazione, molto utile per i siti web in quanto permette di far crescere quasi a piacere la capacità di gestire query in lettura, presenta il non piccolo problema di non essere ridondata sul nodo master. L’implementazione di script che promuovono uno dei nodi slave al ruolo di master nel caso di fault del master precedente presenta moltissimi problemi: si pensi solo alla difficoltà di determinare qual’è lo slave più avanti con la replica e quindi più adatto a prendere il ruolo di master, per non parlare dei problemi nel riattestare tutti gli slave sul nuovo master.

Più praticabile, ma ovviamente molto più costosa, è la possibilità di implementare un cluster classico active-passive con disco condiviso per il solo master. Anche in questo caso comunque alcune attività possono rendersi necessarie sui nodi slave in caso di takeover del master.

Si può poi pensare di estender il caso di configurazione master-master  in una topologia ad anello in cui ogni nodo è replica del precedente e il primo è replica dell’ultimo. Questa configurazione nella pratica non è una grande idea per due ragioni:

  • il sistema nel suo insieme è sempre meno affidabile al crescere del numero dei nodi perché la rottura di un elemento qualsiasi della catena interrompe la propagazione delle modifice e bisogna quindi implementare una logica tutt’altro che banale che automatizzi l’esclusione di nodi rotti. Nodi che soarà poi comunque difficile reinserire senza dare disservizi.
  • ci sono potenziali problemi insidiosi legati al meccanismo di replica di mysql; questo infatti legge tutte le query nel binary log che ha in ingresso e se trova che il primo esecutore di una query è stato un altro nodo la esegue e la scrive nel proprio binary log altrimenti, se cioè riconosce di essere stato il primo esecutore di una query, la scarta. Tutto funziona bene fino a che non muore un nodo. Si pensi a cosa succede se in un sistema di tre server mysql A, B e C configurati ad anello: A master di  B master di C master di A si verifica la seguente situazione. Il nodo A esegue una query di insert e poi muore ad esempio per la rottura di un disco ma solo dopo aver passato l’insert al nodo B. Gli script del sistema riconoscono il problema ed escludono il nodo A rendendo C master di B. B riconosce come non propria la query ricevuta da A, la esegue e la passa a C; C riconosce la query come non propria, la esegue e la passa a B etc in un loop infinito. Per la verità le versioni più recenti di mysql hanno introdotto funzionalità per controllare questo problema ma la situazione rimane delicata.

Un’ultima configurazione che si può pensare di implementare è master-master ( o anche un anello con n master) su cui attestare un gruppo di slave. Gli slave andranno ovviamente divisi tra i master. Il problema di questa configurazione è che se uno dei master viene meno, si perde la possibilità di utilizzare tutti gli slave che a lui fanno capo a meno di non aver implementato una logica che permetta di riattestare gli slave sul master residuo.

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à.