La replicazione è una delle caratteristiche più importanti di MongoDB, soprattutto per la facilità con cui è possibile configurarla, in confronto ad altre soluzioni di storage.
La replicazione ha sostanzialmente lo scopo di rafforzare l’affidabilità del sistema in caso di fallimenti hardware, software o infrastrutturali.
Storicamente MongoDB permette due meccanismi per implementare la replicazione: Master-Slave e Replica Set. Poiché quest'ultima opzione è divenuta lo standard di fatto, nonché la soluzione raccomandata, in questa lezione non affronteremo il sistema Master-Slave, anche se potrebbe capitare di trovarne qualcuno in produzione come retaggio di vecchie installazioni. In tal caso rimandiamo alla guida ufficiale che contiene anche le istruzioni per migrare tale installazione ai Replica Set.
Replica Set
Un Replica Set è sostanzialmente un insieme di istanze MongoDB che contengono gli stessi dati. In un Replica Set è presente sempre una sola istanza detta primaria (primary) e un certo numero di istanze secondarie (secondary). Il funzionamento di base è il seguente:
- il server primario riceve tutte le operazioni di scrittura dai client;
- il server primario effettua la scrittura ed effettua la replica dell’operazione sui server secondari in modo asincrono.
- se, per qualsiasi motivo, il server primario diventa non raggiungibile, gli altri secondari eleggono un primario usando un certo algoritmo, facendo sì che il sistema diventi nuovamente disponibile con tutti i dati preesistenti (Automatic Failover). Opzionalmente è possibile configurare un server arbitro, esclusivamente con il compito di stabilire il nuovo server primario.
Quindi il vantaggio di usare un Replica Set è che far sì che, finché ci sarà un nodo attivo, i dati saranno disponibili.
Per quanto riguarda le letture, per impostazione predefinita i client le inviano al server primario. Tuttavia, al fine di velocizzare le letture e non sovraccaricare il primario, è possibile leggere anche da un nodo secondario, che però deve essere specificato dal client. Questo perché, essendo le replicazioni asincrone, non è detto che tutti i nodi siano aggiornati.
Si noti che le scritture devono sempre e comunque passare per il server primario. Quindi, se nella nostra rete di nodi MongoDB il primario diventasse irraggiungibile perché sovraccaricato dalle scritture, l’unica soluzione sarebbe ricorrere allo sharding, che vedremo nella prossima lezione.
Ricapitolando, configurando un Replica Set, MongoDB ci fornisce automaticamente:
- backup dei dati, uno per ogni server secondario;
- Automatic Failover, ossia la capacità di far sì che la rete continui a funzionare in seguito ad un guasto nel primario;
- la possibilità di leggere dai nodi secondari evitando di sovraccaricare un solo nodo per la lettura;
Configurazione di un Replica Set
Come prima operazione bisogna avviare i server MongoDB che comporranno il nostro Replica Set. Mostreremo la procedura con le convenzioni di Windows, ma per Linux è esattamente la stessa. Avviamo tutti i server, aggiungendo nella riga di comando l’opzione --replSet
con il nome del Replica Set:
> mongod --dbpath C:\mongodata --replSet esempio
Come regola generale è meglio distribuire i server su diverse macchine, per rendere più robusta la nostra configurazione. Tuttavia, se vogliamo avviare i server sulla stessa macchina per fare dei test della configurazione, dobbiamo specificare per ogni server una porta diversa, utilizzando l’opzione --port
.
A questo punto i vari server sono in attesa di essere configurati; quindi, attraverso la console mongo, ci connettiamo ad uno di essi e avviamo la procedura di inizializzazione del Replica Set:
> rs.initiate()
Dopo diversi secondi, durante i quali il server a cui ci siamo connessi si configura come primario, mongo si chiude restituendoci il prompt dei comandi. Se ci riconnettiamo, la console ci appare leggermente diversa:
esempio:PRIMARY>
In questo caso, essa ci informa che siamo connessi al server primario del Replica Set esempio. Ora possiamo aggiungere gli altri nodi, utilizzando il metodo rs.add
:
esempio:PRIMARY> rs.add("SERVER2")
{ "ok" : 1 }
esempio:PRIMARY> rs.add("SERVER1:27018")
{ "ok" : 1 }
esempio:PRIMARY> rs.add("SERVER3")
{ "ok" : 1 }
Nonostante il server ci risponda con ok, in realtà i server impiegheranno qualche secondo per configurarsi come secondari. Si può monitorare lo stato di ognuno di essi con il metodo rs.status
. A operazioni ultimate, l’output dovrebbe somigliare al seguente:
esempio:PRIMARY> rs.status()
{
"set" : "esempio",
"date" : ISODate("2015-02-08T13:27:51Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "SERVER1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 793,
"optimeDate" : ISODate("2015-02-08T13:22:51Z"),
"electionDate" : ISODate("2015-02-08T13:19:34Z"),
"self" : true
},
{
"_id" : 1,
"name" : "SERVER2:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 300,
"optimeDate" : ISODate("2015-02-08T13:22:51Z"),
"lastHeartbeat" : ISODate("2015-02-08T13:27:50Z"),
"lastHeartbeatRecv" : ISODate("2015-02-08T13:27:51Z"),
"pingMs" : 0,
"syncingTo" : "SERVER1:27017"
Il campo stateStr
ci indica lo stato del server. Il metodo rs.add
ci servirà ogni qualvolta avessimo la necessità di aggiungere altri nodi al Replica Set.
Per configurare un arbitro la procedura è simile alla precedente, con l’unica differenza che bisogna usare il metodo rs.addArb
:
esempio:PRIMARY> rs.addArb("SERVER4:27200")
Se vogliamo rimuovere un nodo, possiamo invece utilizzare il metodo rs.remove
:
esempio:PRIMARY> rs.remove("SERVER3")
Lettura da nodi secondari
Per default, la lettura avviene dai nodi primari. Però sia tramite i driver (per cui rimandiamo alla guida ufficiale) sia tramite la shell è possibile specificare una diversa scelta. Ad esempio se vogliamo leggere dal nodo geograficamente più vicino, possiamo scrivere:
esempio:PRIMARY> db.libri.find().readPref("nearest")
In alternativa, se non si vuole sovraccaricare il primario, tranne il caso in cui non ci siano server secondari, si può usare:
esempio:PRIMARY> db.libri.find().readPref("secondaryPreferred")