Limiter la RAM disponible pour Mongo

Pour des besoins de test, et notamment dans le but de mesurer les performances et d’analyser le comportement de Mongo avec différentes quantités de mémoire vive, je me suis penché sur la façon de restreindre les ressources pour un processus donné sous GNU/Linux.

Je me suis d’abord tourné vers ulimit qui semblait permettre de restreindre explicitement la ram, avec en particulier la commande suivante:

ulimit -Hm 30000

Pour la mémoire virtuel, on remplace le m par un v, par exemple:

ulimit -Sv 200000

Cette façon de faire n’était malheureusement pas satisfaisante. En surveillant mon mongo avec mongostat, je constate que celui-ci dépasse largement les limites imposées. Je me tourne donc vers une autre solution: les cgroups.

Reprenons la définition de Wikipédia, les cgroups sont une fonctionnalité du noyau Linux permettant de « limiter, compter et isoler l’utilisation des ressources ». Ça tombe bien, c’est exactement ce que je cherche à faire. En bonus, les cgroups sont utilisés par les LinuX Containers (LXC), procédé de « virtualisation légère », une raison de plus de s’y intéresser.

Revenons à notre problématique de limitation de la ram de mongo. Dans un premier temps, il est nécessaire de préparer l’environnement:

aptitude install libcgroup1

Si le paquet n’est pas disponible sous ce nom là, une simple recherche de cgroup devrait permettre de le trouver. Nous allons tout d’abord créer un nouveau cgroup qui nous permettra d’appliquer les limitations:

sudo cgcreate -t <user> -a <user> -g memory,cpu:<groupname>

Ici, notre cgroup pourra imposer des limites sur la mémoire (memory) et le processeur (cpu).  Nous pouvons maintenant définir les paramètres de notre groupe, en terme de mémoire notamment:

echo 33000000 > /sys/fs/cgroup/memory/<groupname>/memory.limit_in_bytes

Ici, nous limitons la mémoire vive à 33 Mo pour notre cgroup. Des raccourcis existent pour l’écriture des quantités d’octets, on pourrait par exemple remplacer 33000000 par 33M. Il ne nous reste plus qu’à démarrer notre processus mongo avec ces restrictions:

cgexec -g memory:<groupname> mongod

En surveillant notre mongo avec mongostat, nous pouvons ainsi constater une augmentation du nombre de « page faults » lorsque nous réduisons la mémoire disponible de manière significative et constater que mongo prend bien en compte la limite.

Voilà donc un court exemple qui permet d’éviter de saturer sa ram en totalité avant de pouvoir commencer des tests sur mongo, notamment en ce qui concerne l’étude du nombre de « page faults » en fonction du working set et de la ram disponible. Ce procédé peut bien sûr être appliquer à d’autres processus pour éviter qu’ils ne consomment toutes les ressources de la machine. Pour de plus amples d’informations sur le sujet, on se tournera vers le wiki Arch Linux ou encore ce guide Red Hat.

Parsing mongostat data with Logstash

On my way to a complete MongoDB monitoring solution, I’ve been playing with mongostat to see what I can achieve with it. So I tested mongostat on a simple architecture made of two shards, each shard being a replica set composed of tree members. Of course, we also have tree configuration routers and one query router.

First, I discovered a few bugs in the tool when using it with the option discover. This parameter can be used to automatically retrieve statistics from all members of a replica set or a sharded cluster. Using it with version 2.4.9 of mongostat causes some other parameters to be ignored: rowcount and noheaders. So I dove in the code on Github to find that this bugs had been already corrected. We just need modifications to come to the stable version.

mongostat --host localhost:24000 --discover --noheaders -n 2 30 > mongostat.log

Here I’m connecting to my query router, asking mongostat to find other mongoDB instances by itself. Options noheaders and n don’t work at the moment but that’s not a problem. With this setup, I will receive logs every 30s.

There are two types of log: the ones coming from the mongos and the ones coming from the other.

localhost:21000        *0     *0     *0     *0       0     1|0       0   800m  1.04g    30m      0 local:0.0%          0       0|0     0|0   198b   924b    13 rs0  PRI   15:36:55
 localhost:21001        *0     *0     *0     *0       0     1|0       0   800m  1.01g    29m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs0  SEC   15:36:55
 localhost:21002        *0     *0     *0     *0       0     1|0       0   800m  1.01g    29m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs0  SEC   15:36:55
 localhost:21100        *0     *0     *0     *0       0     1|0       0   800m  1.05g    35m      0 local:0.0%          0       0|0     0|0   198b   924b    13 rs1  PRI   15:36:55
 localhost:21101        *0     *0     *0     *0       0     1|0       0   800m  1.01g    34m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs1  SEC   15:36:55
 localhost:21102        *0     *0     *0     *0       0     1|0       0   800m  1.01g    34m      0  test:0.0%          0       0|0     0|0   138b   359b     6 rs1  SEC   15:36:55
 localhost:24000         0      0      0      0       0       0                  174m     5m      0                                             2b    23b     2      RTR   15:36:55

Output from mongostat.

 

The last line coming from the mongos has empty fields. So we will need to deal with it when parsing the log. After having understood how mongostat works, it is now time to see if we can easily plug it in logstash. Let’s take a look at our logstash configuration file.

input {
  file {
    type => "mongostat"
    path => ["/path/to/mongostat.log"]
  }
}

We define where to find the log file.

 

filter {
  if [type] == "mongostat" {
    grok {
      patterns_dir => "./patterns"
      match => ["message", "%{HOSTNAME:host}:%{INT:port}%{SPACE}%{METRIC:insert}%{SPACE}%{METRIC:query}%{SPACE}%{METRIC:update}%{SPACE}%{METRIC:delete}%{SPACE}%{METRIC:getmore}%{SPACE}%{COMMAND:command}%{MONGOTYPE1}%{SIZE:vsize}%{SPACE}%{SIZE:res}%{SPACE}%{NUMBER:fault}%{MONGOTYPE2}%{SIZE:netIn}%{SPACE}%{SIZE:netOut}%{SPACE}%{NUMBER:connections}%{SPACE}%{USERNAME:replicaset}%{SPACE}%{WORD:replicaMember}%{SPACE}%{TIME:time}"]
    }
  }
  if [tags] == "_grokparsefailure" {
    drop { }
  }
  if [message] == "" {
    drop { }
  }
}

We apply filter on each message received if it comes from mongostat. If the message is empty or grok fails to parse it, we drop the log.

 

output {
  stdout { }
  elasticsearch_http {
    host => "127.0.0.1"
  }
}

Simple output. Logs are stored in Elasticsearch so that we can use Kibana to examine them later and are written to stdout for immediate debugging and verification.

Let’s consider a little bit the filter part. We give grok a directory for our personal patterns: ./patterns. This directory contains a file mongostat with the following patterns:

METRIC (\*%{NUMBER})|(%{NUMBER})
COMMAND (%{NUMBER}\|%{NUMBER})|(%{NUMBER})
SIZE (%{NUMBER}[a-z])|(%{NUMBER})
LOCKEDDB (%{WORD}\:%{NUMBER}%)
MONGOTYPE2 (%{SPACE}%{LOCKEDDB:lockedDb}%{SPACE}%{NUMBER:indexMissedPercent}%{SPACE}%{COMMAND:QrQw}%{SPACE}%{COMMAND:ArAw}%{SPACE})|%{SPACE}
MONGOTYPE1 (%{SPACE}%{NUMBER:flushes}%{SPACE}%{SIZE:mapped}%{SPACE})|%{SPACE}

The MONGOTYPE patterns are used to deal with empty fields from mongos log line.
The rest of the match directive is just about capturing each field from mongostat to produce a more readable and analysable output.

To create this, I used an online Grok debugger which is very useful because you needn’t to reload logstash every time you want to test your work. It also provides you instant feedback.

I’m know waiting for the bugs to be fixed in stable so that this solution could be more useful to monitor mongo and maybe use it in production.

List of available patterns on Github.

Easily deploy a local MongoDB cluster

When working with MongoDB, it surely comes a time when you want to try to make it scale. First, by adding replica set, then sharding and at least mix it together to create a simple cluster with replication on each shard. Then, you might want to test monitoring or other functionality, so you will need to launch your cluster, configure it.

If everything goes right, you’ll only need to start all your MongoDB instances. Not a big deal, easy to do it at each restart of your computer, just copy paste the right commands. But what if I need a fresh clean cluster to make my test? To answer this question, I wrote a simple script to easily deploy a local mongo cluster composed of one query router, three configuration routers and two shards, each being a replica set composed of one primary mongod and two secondary mongod.

This simple project is on my Github under the name simple-mongo-cluster. The readme describes on which port you’ll find which mongo. The script provides 5 operations: {init|start|configure|stop|clean}. I will quickly describe each of it.

The init operation creates all directories needed to store logs and data. All mongo instances will be started with the start parameter. To configure the cluster, use configure. For the cluster to be configured, you might need to call configure twice, because of shards sometimes not being added at first call. The stop operation is just about shutting down the whole cluster. At last, clean deletes all directories containing the cluster configuration files, data and logs so that you can start a fresh new cluster.

I hope it will help other developers who want to easily test and discover MongoDB cluster possibilities in a local environment as it helps me. If you have any suggestions, just let me know or fork the project, add your script and send me a pull request.

Mongo Dans Tous Ses Etats – Sharding

La suite de mon étude concernait le processus de sharding. Comme pour la réplication, les informations proviennent cette fois du guide sur le sharding.

Sharding

Le sharding divise la donnée et la distribue sur plusieurs machines, aussi appelé shard et résout ainsi le problème de scaling horizontal. Un cluster est constitué de shard, serveur de configuration et mongos. Le sharding réduit la quantité de donnée que chaque serveur doit stocker, ainsi que le nombre d’opérations à effectuer.

Sharded cluster = shards + query routers (mongos) + config servers

Shard (mongod / Replica Set)

Stock la donnée.
En production, pour des questions de haute disponibilité et de consistence des données, chaque shard est un replica set et le cluster est constitué de deux shards ou plus.

Query Router (mongos)

Sert d’interface avec l’application cliente et dirige les opérations vers le shard approprié. Un cluster peut contenir plusieurs routeurs pour distribuer la charge.

En production, un mongos ou plus.
Les curseurs et d’autres ressources étant spécifique à une instance de mongos, chaque client doit interagir avec seulement un mongos.

Config Server

Stock les métadatas du cluster.
Contient un mapping entre les données du cluster et les shards (Quelle donnée se trouve sur quel shard). En production, un cluster possède exactement 3 config servers.

Si un ou deux serveurs de configuration deviennent indisponible, les métadatas du cluster passe en lecture seule jusqu’à ce que les trois serveurs soient à nouveau disponible. Il est toujours possible de lire et d’écrire sur les shards mais aucune migration et aucun découpage n’aura lieu sur les chunks.

Les sauvegardes de ces serveurs sont critiques.
Si le nom ou l’adresse qu’un cluster utilise pour se connecter à un serveur de configuration change, il est nécessaire de redémarrer tous les mongod et les mongos! D’où l’utilité des CNAMEs pour identifier les serveurs de configuration.

Chaque serveur doit être sur une machine séparée.

Partitionnement des données

Le partitionnement s’effectue en utilisant une clé de sharding (sharding key).

Shard Key

Soit un champ indexé ou champ indexé composé existant dans tous les documents de la collection. MongoDB divise la shard key en morceaux (chunks) et les distribue de manière égale entre les shards. La division de la clé s’effectue soit avec un range based partitioning ou un hash based partitioning.

Pour sélectionner une shard key, déterminer les champs communément inclut dans les requêtes pour une application précise et déterminer quelles opérations demandent le plus de performance.

L’index sur la shard key ne peut pas être un multikey index.

Range Based Sharding

Séparation des données en plusieurs intervalles. Deux documents avec une clé de sharding proche ont de grandes chances d’être dans le même chunk. Néanmoins, il peut en résulter une distribution inégale des données sur les shards. Plus efficace en cas de requêtes sur des intervalles. Le router peut déterminé plus facilement vers quels shards diriger la requête.

Hash Based Sharding

MongoDB calcule un hash de la valeur du champ et utilise ces hashs pour créer les chunks. Deux documents avec une clé de sharding proche ont très peu de chance d’être dans le même chunk. Cela permet de s’assurer d’une distribution plus aléatoire des données au sein du cluster et assure une distribution régulière des données sur les shards. Une requête sur un intervalle à de grande chance de s’adresser à tous les shards.

Mécanismes

Mongo dispose de mécanismes pour éviter qu’un shard devienne trop grand: splitting et le balancer.

Splitting

Lorsqu’un chunk devient trop grand, mongo le divise en deux.
Ceci n’affecte pas les données ou les shards.

Balancing

Permet de migrer des chunks. Lorsque la distribution des données devient irrégulière, le balancer va migrer des chunks du shard avec le plus grande nombre de chunks à celui qui en possède le moins. Les données sont retirés du shard d’origine après une migration totale et réussie des données.

Mongo Dans Tous Ses Etats – Réplication

Cette semaine, j’ai eu l’occasion de commencer à étudier le fonctionnement de la réplication et du sharding avec MongoDB. Voici diverses informations provenant du guide sur la réplication.

Réplication

La réplication consiste à écrire la donnée sur plusieurs serveurs et répond ainsi au problématique de haute disponibilité et de redondance des données. Elle permet, dans certains cas, d’augmenter les capacités en lecture. Un replica set peut avoir jusqu’à 12 membres dont 7 voteront à la fois. Pour disposer de plus de 12 instances, il est nécessaire de passer à la réplication maître-esclave.

L’architecture standard pour un replica set en production se compose de trois membres. Si l’application se connecte à plusieurs replica set, chacun doit avoir un nom différent. Les membres s’envoient des ping toutes les 2 secondes.

Replica Set: Groupe de mongod disposant des mêmes (~~) données.

Membres

On distingue deux types principaux de mongod dans un replica set: les primaires et les secondaires.

Primaire (Primary)

Il est unique et reçoit toutes les opérations d’écriture. Tous les changements sont conservés dans son oplog. Une élection d’un nouveau mongod primaire à lieux, si l’actuel ne communique avec aucun membre du replica set pendant 10s.

Secondaire (Secondary)

Récupère le oplog et applique les opérations sur son jeu de données. En cas d’indisponibilité du primaire, un mongod secondaire sera élu à sa place.

Membre à priorité 0

Mongod secondaire qui ne peut pas devenir primaire et ne peut déclencher d’élections. Permet de s’assurer que seul les membres qui en ont les capacités deviendront primaire (distribution géographique, hardware). Il est conseillé de s’assurer que le datacenter principal contient les votants et les membres éligibles.

Membre caché

Invisible pour les applications clientes. Sont toujours des membres à priorité 0, ne peuvent devenir primaire mais peuvent voter. En cluster, mongos n’interagit pas avec les membres cachés.

Membre retardé

Reflète un état retardé de l’état du groupe.
Requis:
* Doit être un membre à priorité 0
* Devrait être un membre caché
Le délai doit être plus petit que la capacité de oplog. Cette fonctionnalité est utile pour pouvoir appliquer un rollback (retour en arrière) en cas d’erreur humaine.

Arbitre (Arbiter)

Sa fonction est de voter en cas d’élection d’un mongod primaire et permet d’obtenir une majorité en cas d’un nombre pair de membres.
Important: Ne pas ajouter d’arbitre sur les machines hébergeant déjà un primaire ou un secondaire.

Stratégies

Déployer un nombre impair de membres pour s’assurer qu’un primaire pourra toujours être élu. Ajouter un arbitre si besoin.
Tolérance aux fautes: Correspond au nombre de membres auquel on soustrait la majorité requise pour élire un nouveau mongod primaire.
Au moins un membre à priorité 0 dans un autre datacenter. Garder une majorité de membre au même endroit.

Le sharding est souvent une meilleur stratégie pour augmenter les capacités en lecture et en écriture.

Read Preference Mode

* primary
* primaryPreferred
* secondary
* secondaryPreferred
* nearest
Seul primary permet d’être sûr d’avoir des données totalement à jour.
Utiliser readPref() dans mongo shell pour accéder aux préférences.