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.

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.