awk e linea di comando

La shell di UNIX/Linux è uno strumento potentissimo che permette di velocizzare ed automatizzare le più svariate operazione. Dato che in UNIX ogni cosa è un file, sono di fondamentale importanza quegli strumenti che permettono di analizzare e gestire i file.

Spessissimo i file con cui si ha a che fare nelle attività sistemistiche sono dei rudimentali database strutturati con i record in righe e i campi in colonne. In questa categoria rientrano ad esempio i file passwd e moltissimi file di log.

Per poter automatizzare operazioni su questo tipo di file è necessario uno strumento che dia accesso a specifici campi nei vari record. Come sempre in ambiente UNIX/Linux è possibile procedere in diversi modi ma uno strumento di sicuoro interesse è awk. Si tratta di uno di quei tool la cui conoscenza non dovrebbe mancare a nessun sistemista.

Nella sua forma più semplice awk accetta in stdin un file, e permette di estrarre dei campi da ogni riga.

Ed esempio se volessi estrarre gli utenti che hanno script schedulati nel seguente file crontab:

17 *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

potrei utilizzare la seguente righa di comando:

cat /etc/crontab | awk '{print $6}'

all’interno di awk ogni campo, infatti, è contenuto in una variabile $n, dove n è il numero del campo ed i campi sono separati de default da spazi. L’intera riga è contenuta nella variabile $0. Ci sono molte altre variabili predefinite in awk e la lista completa è riportata nel suo manuale (man awk). Una menzione la merita $NR che contiene il numero della riga che si sta elaborando.

Non in tutti i file il separatore è lo spazio; l’opzione -F permette di scegliere un separatore differente.

awk può essere utilizzato come filtro sfruttando il costrutto if al suo interno:

Ad esempio se voglio sapere quali utenti non hanno una shell reale posso utilizzare:

cat /etc/passwd|awk -F: '{if ($7=="/bin/false") print $1}'

Di grande interesse anche i costrutti BEGIN ed END che permettono di definire blocchi di operazioni da eseguire prima o dopo l’esame del file. Posso ad esempio riscrivere lo stesso esempio di prima come:

cat /etc/passwd|awk 'BEGIN{FS=":"}{if ($7=="/bin/false") print $1}'

awk è un vero e proprio linguaggio di programmazione anche se abbastanza semplice, e le possibilità che offre sono molto ampie; nondimeno la sua utilità è soprattutto nelle sue forme più semplici dove, con molta semplicità, permette di improvvisare filtri o operazioni anche molto sofisticati.

Concludo con un esempio dove mostro come awk può essere utilizzato per calcolare la durata media delle slow query a partire da un file di log di mysql:

cat mysql-slow.log |grep Query_time|awk '{cont++;totale+=$3}END{media=totale/cont; print "media = " media}'

MySql High Availability

Voglio qui parlare di “Mysql High Availability”, un libro scritto da Charles Bell, Mats Kindahl e Lars Thalmann ed edito da o’reilly, che ho trovato veramente utile ed illuminante.

Premesso che presuppone una buona conoscenza di MySql, affronta con notevole precisione e ricchezza tutta una serie di argomenti avanzati ma fondamentali se si ha la necessità di lavorare con architetture non banali.

In modo particolare viene affrontata nel dettaglio tutto quello che riguarda la replica e vengono esaminate tutta una serie di soluzioni per implementare architetture che scalano in lettura ma anche in scrittura.

Per meglio comprendere queste architetture vengono esaminati nel dettaglio il processo di replica ed i binary log ed il loro uso per nel Disaster Recovery.

La seconda parte del libro è dedicata al monitoraggio, al troubleshooting legato alla replica ed al disaster recovery.

Un ultima parte è dedicata all’uso di mysql in ambienti cloud e all’introduzione di MySQL Cluster.

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

perl e linea di comando

Il perl con le sue espressioni regolari è anche un fantastico tool per la linea di comando in grado di sostituire e surclassare i pur notevoli awk e sed. Usato con le opzioni del caso, infatti, il perl ne riprende tutte le caratteristiche ma aggiunge la sua flessibilità e ricchezza.

Le opzioni chiave per l’uso da linea di comando sono “-p” e “-n“: in entrambi i casi lo script o il comando in perl viene eseguito per ogni riga del file in ingresso ma nel secondo caso il risultato va in output.

Queste opzioni corrispondono ai loop che riporto sotto:

-n

LINE:
while (<>) {
        ...             # programma
      }

-p

LINE:
while (<>) {
         ...             # programma
      } continue {
         print or die "-p destination: $!\n";
      }

Molto utili sono anche

  • -e che permette di inserire direttamente sulla linea di comando il codice perl da eseguire
  • -i che fa si che venga modificato direttamente il file che viene letto

Ad esempio entrambi i seguenti comandi

cat /etc/passwd |perl -pe 's/(.*):(.*):(.*):(.*):(.*):(.*):(.*)/$1\t\t$6/'
cat /etc/passwd | awk -F: '{print $1"\t\t"$6}'

permettono di estrarre dal file password gli utenti e le loro home.

Perl, utilizzato in questo modo, permette di definire dei blocchi di codice BEGIN e END per eventuali operazioni preliminari a o conclusive del loop implicito: si immagini il caso in cui si voglia contare il numero di occorrenze di un qualche pattern in un file.

La documentazione delle opzioni è su perldoc perlrun.

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