Howto – ambiente di test – mysql – replica master-master

Vediamo ora come implementare una replica master-master per MySQL.

Per prima cosa bisogna scaricare  il database MySQL. Per questo ambiente di test scarichiamo il pacchetto binario dal sito MySQL,

Ad esempio su di una distribuzione debian-like procederemo come al solito con

aptitude search mysql 
aptitude install mysql-server

Andiamo ora a creare due istanze del db.

Creiamo come root le datadir

mkdir /var/lib/mysql-mm-1
mkdir /var/lib/mysql-mm-2

Sempre come root prepariamo le configurazioni

cp -rp /etc/mysql/my.cnf /etc/mysql-mm-1/my.cnf
cp -rp /etc/mysql/my.cnf /etc/mysql-mm-2/my.cnf

Editiamo ora il file my.cnf fino ad ottenere per il nodo 1 qualche cosa come

[client]
port            = 3307
socket          = /var/run/mysqld/mysqld-mm-1.sock

[mysqld_safe]
socket          = /var/run/mysqld/mysqld-mm-1.sock
nice            = 0

[mysqld]
user            = mysql
pid-file        = /var/run/mysqld/mysqld-mm-1.pid
socket          = /var/run/mysqld/mysqld-mm-1.sock
port            = 3307
basedir         = /usr
datadir         = /var/lib/mysql-mm-1
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
key_buffer              = 16M
max_allowed_packet      = 16M
thread_stack            = 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit       = 1M
query_cache_size        = 16M
log_error = /var/log/mysql/error-mm-1.log
server-id               = 11
log_bin                 = /var/log/mysql/mm-1-bin.log
expire_logs_days        = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet      = 16M

[mysql]

[isamchk]
key_buffer              = 16M

e per il nodo 2 qualche cosa come

[client]
port		= 3308
socket		= /var/run/mysqld/mysqld-mm-2.sock

[mysqld_safe]
socket		= /var/run/mysqld/mysqldi-mm-2.sock
nice		= 0

[mysqld]
user		= mysql
pid-file	= /var/run/mysqld/mysqld-mm-2.pid
socket		= /var/run/mysqld/mysqld-mm-2.sock
port		= 3308
basedir		= /usr
datadir		= /var/lib/mysql-mm-2
tmpdir		= /tmp
lc-messages-dir	= /usr/share/mysql
skip-external-locking
bind-address		= 127.0.0.1
key_buffer		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8
myisam-recover         = BACKUP
query_cache_limit	= 1M
query_cache_size        = 16M
log_error = /var/log/mysql/error-mm-2.log
server-id		= 12
log_bin			= /var/log/mysql/mm-2-bin.log
expire_logs_days	= 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet	= 16M

[mysql]
[isamchk]
key_buffer		= 16M

da notare in questi file:

  • port: ho usato porte non standard e diverse per i due nodi
  • server-id: ho dato due valori diversi ai due nodi
  • log-bin: ho abilitato i binary log
  • datadir: specifica per ogni nodo.

Devo ora popolare le directory dei dati:

mysql_install_db --user=mysql  --defaults-file=/etc/mysql-mm-1/my.cnf
mysql_install_db --user=mysql  --defaults-file=/etc/mysql-mm-2/my.cnf

Posso ora avviare i due database

mysqld_safe --defaults-file=/etc/mysql-mm-1/my.cnf &
mysqld_safe --defaults-file=/etc/mysql-mm-2/my.cnf &

Al primo avvio dovranno essere generati tutta una serie di file ma dopo un po’ i due database dovrebbero essere accessibili:

mysql --port 3307 --host 127.0.0.1 --user=root
mysql --port 3308 --host 127.0.0.1 --user=root

NB: su Ubuntu apparmor impedisce di eseguire questa procedura. Per escluderlo

aa-complain /usr/sbin/mysqld

Trattandosi di due istanze appena create non ci dobbiamo preoccupare allineare la situazione iniziale dei due nodi mysql imprtando dei backup.

Procediamo quindi a configurare il nodo mysql due come replica del nodo mysql uno.

Per prima cosa dobbiamo vedere lo stato come master del nodo uno:

mysql --port 3307 --host 127.0.0.1 --user=root
show master status\G

otterremo qualche cosa come

*************************** 1. row ***************************
            File: mm-1-bin.000005
        Position: 107
    Binlog_Do_DB: 
Binlog_Ignore_DB: 
1 row in set (0.00 sec)

Nel caso avessimo ripristinato da un backup avremmo dovuto far riferimento a informazioni analoghe ma prese al momento del backup.
Abilitiamo ora la prima replica

mysql --port 3307 --host 127.0.0.1 --user=root
change master to MASTER_HOST='127.0.0.1', MASTER_PORT=3307 , MASTER_USER='root' , MASTER_LOG_FILE = 'mm-1-bin.000005', MASTER_LOG_POS =107;
show slave status\G

Ho qui utilizzato l’utente root. In condizioni non di test bisognerebbe utilizzare un utente con gli appositi permessi (REPLICATION_CLIENT, REPLICATION_SLAVE).
Lo stesso per l’altro nodo

mysql --port 3308 --host 127.0.0.1 --user=root
change master to MASTER_HOST='127.0.0.1', MASTER_PORT=3308 , MASTER_USER='root' , MASTER_LOG_FILE = 'mm-2-bin.000005', MASTER_LOG_POS =107;
show slave status\G

perl e memcached

Creaiamo un ambiente di test  per memcached e testiamolo con perl.

Utilizzeremo:

  • memcahced
  • perl
  • il modulo perl Cache::Memcached
  • il modulo perl String::Random
  • il comando di monitoraggio memcache-top

Do per scontato che perl sia installato. Questo è, credo, praticamente sempre vero in ambiente UNIX/LInux.

Potrebbe non essere invece presente il modulo perl per la gestione della cache con memcached. Lo si può istallare o come pacchetto dell distribuzione (es. su Debian/Ubuntu qualche cosa come sudo aptitude install libcache-memcached-perl)  o come modulo cpan:

$ cpan
cpan[1]> install Cache::Memcached

Analogamente istalliamo il modulo String::Random (libstring-random-perl).

Passiamo ora al daemone.memcached. Consideriamo come sempre un sistema Debian/Ubuntu dove basterà:

# aptitude update
# aptitude install memcached
# update-rc.d -f memcached remove
# /etc/init.d/memcached stop

Il primo comando aggiorna le liste dei pacchetti, il secondo istalla il demone. Dato che stiamo lavorando su di un ambiente di test, con il terzo comando si facciamo in modo che il demone non venga avviato all’avvio del pc e con l’ultimo fermiamo il demone lanciato con le configurazioni di default.

Memcached è presente in quasi tutte le distribuzioni e se non si utilizza un sistema Debian o derivato si può far ricorso al sistema di pacchettizzazione della propria distribuzione o anche ai sorgenti del demone recuperabili dal sito ufficiale.

L’ultimo elemento per l’ambiente di test è memcache-top, che può essere scaricato dal sito ufficiale.

wget -O memcache-top  http://memcache-top.googlecode.com/files/memcache-top-v0.6

Ora che abbiamo tutti gli elementi possiamo aprire una shell e avviare 3 demoni per avere un pool non banale. Li metteremo in ascolto su localhost ma, ovviamente, su porte differenti.

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

possimao poi avviare in un’altra shell memcache-top con cui monitereremo il nostro pool di server

memcache-top --commands --instance=127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213

Vediamo ora un primo semplice script perl che chiameremo memcahced.pl:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

$memd->set("my_key", "Some Value\n");
my $val = $memd->get("my_key");
if ($val) { print $val; }

In dettaglio:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;

Richiama l’eseguibile attivando i warning e i moduli strict e Memcached.

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

definisce l’oggetto di connessione a memcached inserendo una lista di server.

$memd->set("my_key", "Some Value\n");
my $val = $memd->get("my_key");
if ($val) { print $val; }

scrive una chiave su memcached e la rilegge andando poi a scrivere a video il risultato.
Per eseguire questo script non bisogna dimenticare di rendere eseguibile il file o richiamarlo esplicitamente con l’interprete perl.

Con una singola esecuzione memcache-top non dice gran che ma se si esegue ripetutamente lo script ad esempio con

while [ true ]; do ./memcached.pl ; done

si vedranno numerosi accessi ad un singolo server; come ci si sarebbe dovuti aspettare trattandosi di una sola chiave.
Nel mio caso memcahce-top riporta:

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

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.0%	0.0%	5	1.0ms	0.0	0	0	2	344	
127.0.0.1:11212		0.0%	0.0%	5	0.8ms	0.0	0	0	2	344	
127.0.0.1:11213		0.0%	0.0%	5	0.6ms	0.0	14	14	647	992	

AVERAGE:		0.0%	0.0%	5	0.8ms	0.0	5	5	217	560	

TOTAL:		83B/	0.2GB		15	2.4ms	0.0	14	14	651	1680	
(ctrl-c to quit.)

ma soprattutto dopo aver ucciso il demone in ascolto sulla porta 11213

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

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.0%	0.0%	5	0.6ms	0.0	0	0	2	344	
127.0.0.1:11212		0.0%	0.0%	5	0.4ms	0.0	15	15	662	1007	
127.0.0.1:11213 is DOWN.

AVERAGE:		0.0%	0.0%	3	0.3ms	0.0	5	5	221	450	

TOTAL:		83B/	0.1GB		10	1.0ms	0.0	15	15	664	1351	
(ctrl-c to quit.)

dove so vede che le connessioni si sono spostate su di un’altro demone. Su questo aspetto bisogna fare molta attenzione dato che in un sistema di produzione la gestione dei fault  è cruciale e dato che questo aspetto è demandato alla libreria del linguaggio che si utilizza  bisogna averne molto chiaro il comportamento. Vale comunque in generale che bisognerebbe avere in numero sufficiente di server per cui se se ne dovesse perdere uno i restanti dovrebbero essere sufficienti a svolgere i loro compiti; in altre parole è molto meglio distribuire il servizio su molti serevr che averne uno o due dedicati.

Per vedere in che modo le chiavi si distribuiscono sul cluster ora inseriamo un valore fisso in una chiave di nome casuale (la distribuzione è legata al nome della chiave). Per farlo utilizziamo un altro script perl che chiameremo memcahced_random.pl:

#!/usr/bin/perl -w

use strict;
use Cache::Memcached;
use String::Random;

my $memd = new Cache::Memcached {
        'servers' => [ "localhost:11211", "localhost:11212", "localhost:11213" ],
        'debug' => 0,
        'compress_threshold' => 10_000,
};

my $foo = new String::Random;
my $random_key = $foo->randpattern("...");
my $value = "chiave casuale";

$memd->set("$random_key", "$value\n");
my $val = $memd->get("$random_key");
if ($val) { print $val; }

Non è molto diverso dal precedente tranne nella riga:

my $random_key = $foo->randpattern("...");

in cui viene generata casualmente una stringa di 3 caratteri da utilizzare come nome della chiave.
Anche in questo caso conviene inserire lo script in un loop per visualizzarne meglio gli effetti con memcache-top:

while [ true ]; do ./memcached_random.pl; done

Le connessioni si andranno a distribuire sui vari nodi in modo ragionevolmente equilibrato

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

INSTANCE		USAGE	HIT %	CONN	TIME	EVICT/s GETS/s	SETS/s	READ/s	WRITE/s	
127.0.0.1:11211		0.1%	0.0%	5	0.9ms	0.0	7	7	317	686	
127.0.0.1:11212		0.1%	0.0%	5	0.6ms	0.0	9	9	375	748	
127.0.0.1:11213		0.1%	0.0%	5	0.6ms	0.0	9	9	403	779	

AVERAGE:		0.1%	0.0%	5	0.7ms	0.0	8	8	365	738	

TOTAL:		132.7KB/	0.2GB		15	2.2ms	0.0	25	25	1095	2213	
(ctrl-c to quit.)

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.

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