Réplication Dans MongoDB¶
Définition¶
La réplication est une technique commune aux systèmes NoSQL pour assurer la sécurité et la reprise après les pannes. Elle consiste à créer des copies des données sur des serveurs différents pouvant remplacer le serveur en cas de panne.
Principe du Replica Set¶
- Un Replica Set est un groupe de serveurs mongod qui gèrent les mêmes données.
- Il est composé d'un unique serveur primaire (maître) et de plusieurs serveurs secondaires (esclaves).
- Le serveur primaire répond aux demandes du (lecture/écriture) client (driver). Le client peut également lire à partir des serveurs secondaires.
- Les serveurs secondaires reproduisent les mêmes opérations (écritures) réalisées au niveau du serveur primaire.
- Si le serveur primaire est indisponible, un serveur secondaire est promu en primaire après une procédure d'élection.
- Si le nombre de serveurs est pair, un serveur arbitre est ajouté.
- Le nombre minimal de serveurs est de 3 et ils peuvent atteindre 50.
- 7 serveurs au maximum peuvent participer à une élection.
Mise en place d'un Replica Set avec Docker ¶
La création d'un cluster de 3 noeuds avec Docker consiste à réaliser les 3 étapes suivantes :
- Créer un réseau Docker pour interconnecter les containers
- Démarrer 3 instances de MongoDB
- Initier le Replica Set
Créer un réseau docker
docker network create mongoCluster
docker run -d -p 27017:27017 --name mongo1 --network mongoCluster mongo:6.0.1 mongod --replSet rs1 --bind_ip localhost,mongo1
docker run -d -p 27018:27017 --name mongo2 --network mongoCluster mongo:6.0.1 mongod --replSet rs1 --bind_ip localhost,mongo2
docker run -d -p 27019:27017 --name mongo3 --network mongoCluster mongo:6.0.1 mongod --replSet rs1 --bind_ip localhost,mongo3
Initier le Replica Set
-
Se connecter à mongo1 avec Compass (mongodb://localhost:27017) ou avec la commande
docker exec -it mongo1 mongosh
-
Exécuter depuis le shell
> rs.initiate({ _id: "rs1", members: [ {_id: 0, host: "mongo1"}, {_id: 1, host: "mongo2"}, {_id: 2, host: "mongo3"} ] })
-
Vérifier le replica
La réponse est similaire à ce qui suit :> rs.status()
{
set: 'rs1',
date: 2022-10-12T09:56:20.947Z,
myState: 1,
term: Long("1"),
syncSourceHost: '',
syncSourceId: -1,
heartbeatIntervalMillis: Long("2000"),
majorityVoteCount: 2,
writeMajorityCount: 2,
votingMembersCount: 3,
writableVotingMembersCount: 3,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1665568580, i: 7 }), t: Long("1") },
lastCommittedWallTime: 2022-10-12T09:56:20.382Z,
readConcernMajorityOpTime: { ts: Timestamp({ t: 1665568580, i: 7 }), t: Long("1") },
appliedOpTime: { ts: Timestamp({ t: 1665568580, i: 7 }), t: Long("1") },
durableOpTime: { ts: Timestamp({ t: 1665568580, i: 7 }), t: Long("1") },
lastAppliedWallTime: 2022-10-12T09:56:20.382Z,
lastDurableWallTime: 2022-10-12T09:56:20.382Z
},
lastStableRecoveryTimestamp: Timestamp({ t: 1665568522, i: 1 }),
electionCandidateMetrics: {
lastElectionReason: 'electionTimeout',
lastElectionDate: 2022-10-12T09:52:42.586Z,
electionTerm: Long("1"),
lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1665568351, i: 1 }), t: Long("-1") },
lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1665568351, i: 1 }), t: Long("-1") },
numVotesNeeded: 2,
priorityAtElection: 1,
electionTimeoutMillis: Long("10000"),
numCatchUpOps: Long("0"),
newTermStartDate: 2022-10-12T09:52:42.779Z,
wMajorityWriteAvailabilityDate: 2022-10-12T09:52:44.189Z
},
members: [
{
_id: 0,
name: 'mongo1:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 310,
optime: [Object],
optimeDate: 2022-10-12T09:56:20.000Z,
lastAppliedWallTime: 2022-10-12T09:56:20.382Z,
lastDurableWallTime: 2022-10-12T09:56:20.382Z,
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1665568362, i: 1 }),
electionDate: 2022-10-12T09:52:42.000Z,
configVersion: 1,
configTerm: 1,
self: true,
lastHeartbeatMessage: ''
},
{
_id: 1,
name: 'mongo2:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 229,
optime: [Object],
optimeDurable: [Object],
optimeDate: 2022-10-12T09:56:20.000Z,
optimeDurableDate: 2022-10-12T09:56:20.000Z,
lastAppliedWallTime: 2022-10-12T09:56:20.382Z,
lastDurableWallTime: 2022-10-12T09:56:20.382Z,
lastHeartbeat: 2022-10-12T09:56:20.778Z,
lastHeartbeatRecv: 2022-10-12T09:56:20.316Z,
pingMs: Long("0"),
lastHeartbeatMessage: '',
syncSourceHost: 'mongo1:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 1,
configTerm: 1
},
{
_id: 2,
name: 'mongo3:27017',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 229,
optime: [Object],
optimeDurable: [Object],
optimeDate: 2022-10-12T09:56:20.000Z,
optimeDurableDate: 2022-10-12T09:56:20.000Z,
lastAppliedWallTime: 2022-10-12T09:56:20.382Z,
lastDurableWallTime: 2022-10-12T09:56:20.382Z,
lastHeartbeat: 2022-10-12T09:56:20.778Z,
lastHeartbeatRecv: 2022-10-12T09:56:20.317Z,
pingMs: Long("0"),
lastHeartbeatMessage: '',
syncSourceHost: 'mongo1:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 1,
configTerm: 1
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1665568580, i: 7 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1665568580, i: 7 })
}
Test du Replica Set¶
Test des opérations de lecture/écriture¶
Essayer les opérations suivantes :
Se connecter au serveur primaire et créer une base de données 'Courrier' en suivant les étapes suivantes :
> use courrier
> db.createCollection('mail')
> db.mail.insert({from:'support@mongodb.com',subject:'Replica Set Testing',body:'OK'})
Se connecter aux serveurs secondaires et vérifier le contenu de la base :
> use courrier
> db.mail.find()
Read Preference
Par défaut, la lecture sur Replica Set est autorisée sur le serveur Primary. Pour éviter cette erreur, exécuter > db.getMongo().setReadperf('secondary')
et réessayer.
Vérifier si l'insertion est possible depuis un serveur secondaire.
Test de la reprise sur panne¶
Pour simuler une panne :
- Se connecter au serveur primaire.
- Arrêter le serveur et examiner le comportement du Replica Set (réponse aux requête, élection d'un nouveau serveur primaire).
> use admin
> db.shutdownServer()
Le fichier oplog¶
Le serveur primaire crée automatiquement une collection oplog.rs dans la base de données local pour y enregistrer toutes les opérations de mise à jour. Les serveurs secondaires lisent régulièrement le fichier oplog pour répliquer les données.
Afficher le contenu de cette collection :
> use local
> db.oplog.rs.find().pretty()
- "op" : code opération
- "ts" : date et heure
- "ns" : collection concernée
Pour fixer la taille de ce fichier modifier le paramètre oplogSizeMB
replication:
oplogSizeMB: <taille en MB>
Haute disponibilité et modes de lectures¶
Pour améliorer le temps de réponse des opérations de lecture (l'écriture est exclusivement exécutée sur le serveur primaire). Plusieurs modes d'exécution des lectures sont possibles à travers le pramètre localThresholdMS
de la section replication à savoir :
- Primary : valeur par défaut, lecture sur le serveur d’écriture.
- PrimaryPreferred : si jamais le PRIMARY n’est plus disponible, les requêtes sont routées vers le SECONDARY.
- Secondary : Routé uniquement sur les SECONDARY.
- Nearest : Le serveur physique le plus proche sur le réseau (latence la plus faible) est interrogé directement par le client.
D'autres paramètres permettent aussi d'influencer la haute disponibilité comme la priorité d'élection de chaque serveurs et le paramètre readConcern.
Se connecter au primary
docker exec -it mongo1 mongosh
Changer le mode de lecture
use courrier;
db.getMongo().setReadPref('primary');
db.getMongo().setReadPref('secondary');
db.getMongo().setReadPref('primaryPreferred');
db.getMongo().setReadPref('secondaryPreferred');
docker exec -it mongo2 mongosh
puis
use courrier;
db.mail.find();