Speed up your CFEngine by using a RAM disk!

Lors de l’utilisation de CFEngine au jour le jour avec un set important de promesses, il est possible un jour de rencontrer le problème suivant, sépcialement sur les anciennes versions de CFEngine: les bases de données internes de celui-ci peuvent devenir lentes avec le temps et consommer beaucoup d’entrées/sorties disque. Sur une machine standard, cela ne posera pas de problème mais sur une machine fortement chargée comme un centralisateur de journaux, un hyperviseur de machines virtuelles ou un système de base de données, cela peut mettre CFEngine en défaut.

Une solution que nous avons testée est d’utiliser un tmpfs (ramdisk) pour le dossier d’état de CFEngine.

RAM disks!?

L’idée est assez simple: si vous avez une machine chargée en entrées/sorties, et qu’il petit démon souffre de cette charge alors qu’il n’utilise que des bases de données non critique de quelques mégaoctets, pourquoi ne pas les charger directement en mémoire vive ?

C’est ici que le concept de RAMdisk entre en jeu: il sert a créer un point de montage spécial sur la machine, utilisable comme n’importe quel autre système de fichiers mais stockant le contenu de celui-ci en mémoire vive. De cette manière, en échange d’un petit morceau de votre espace mémoire total, vous obtenez un petit espace pour stocker des fichiers qui n’utiliseront pas de ressources disque du tout, et qui est extrêmement rapide (voir: https://forums.bukkit.org/threads/harddrive-vs-ramdisk-vs-tmpfs-benchmark.2710/).

Bien sûr, il y a un inconvénient: le contenu du RAMdisk est perdu a chaque fois qu’il est démonté. Il est purement volatile et ne devrait surtout pas être utilisé pour des données importantes a moins que vous effectuiez une synchonisation régulière du contenu sur un média persistant comme un disque dur ou un SSD.

Voici une application intéressante des RAMdisks pour rendre l’utilisation des navigateurs internet plus rapides: https://wiki.archlinux.org/index.php/Profile-sync-daemon

Comme vous pouvez le voir, les RAMdisks sont vraiment l’outil idéal pour résoudre ce souci d’entrées/sorties sur CFEngine.

Utiliser des RAM disks avec CFEngine

Maintenant, venons en aux faits: Je possède une machine Linux sur un hyperviseur chargé et mon CFEngine a beaucoup de promesses a appliquer. Problème: L’exécution de l’agent est lente et strace m’indique que le goulet d’étranglement se situe au niveau des fichiers /var/cfengine/state/cf_state.tcdb et /var/cfengine/state/cf_lock.tcdb. En effet, beaucoup de temps est dépensé a essayer de stocker des choses dans ces bases de données, alors que le disque dur peine a fournir de la bande passante pour les machines virtuelles.

Comme expliqué dans le manuel de référence de CFEngine, ce répertoire contient les données dont CFEngine a besoin pour garder la trace de l’état de la machine. Ces fichiers (cf_locks.tcdb et cf_state.tcdb) sont utilisés pour stocker des informations sur les classes persitantes et les verrous, ainsi que diverses données internes comme les métriques de cf-monitord et les données “last seen” pour les communications entre pairs. Vous avez aussi a cet endroit un cache pour les paquetages, utilisé par les bodies package_method. (software_packages.csv)
Vous pouvez jeter un oeil au contenu des bases de données en utilisant l’outil “tchmgr” fourni avec les paquetages de cfengine.com ou votre gestionnaire de paquetages.

De fait, il est relativement sans danger de stocker ces fichiers dans un endroit temporaire tant que la machine ne redémarre pas trop souvent, a moins que vous ne vous utilisiez beaucoup de classes persistantes ou que vous aviez besoin de conserver les occurences les plus récentes des échanges entre pairs avec cf-key. La conséquence de mettre ce répertoire dans un RAMdisk est que CFEngine va complètement oublier ce qu’il sait a propos de la machine en cas de redémarrage et se comporter comme si il venait d’être installé ou comme si vous aviez utilisé l’option -K (ignorer les verrous).

Sur Linux, la méthode la plus rapide et facile de déployer un RAMdisk est d’utiliser un tmpfs: http://fr.wikipedia.org/wiki/Tmpfs

Maintenant, voici comment monter un tmpfs de 128M sur /var/cfengine/state:

/bin/mount -t tmpfs -o size=128M,nr_inodes=2k,mode=0700,noexec,nosuid,noatime,nodiratime tmpfs /var/cfengine/state

Et voilà, c’est tout. Vous pouvez éventuellement rajouter un cronjob afin de synchoniser périodiquement ce répertoire avec un autre, “au cas ou”.

Résultats

Et bien, c’est assez facile a constater avec: “/usr/bin/time sudo /opt/rudder/bin/cf-agent -KI”

Avant:

4.92 user
0.40 system
0:06.65 elapsed
80% CPU (0 avgtext + 0 avgdata 150544 maxresident)k
0 inputs + 1952 outputs (0 major+ 27848 minor)pagefaults 0 swaps

Après:

1.94 user
0.23 system
0:02.71 elapsed
80% CPU (0 avgtext + 0 avgdata 150528 maxresident)k
0 inputs + 1136 outputs (0 major + 27817 minor) pagefaults 0 swaps

Those results where obtained on a “calm” machine, the difference can exponentially grow as the I/O usage gets more intensive, especially if you are getting close to your storage backend limits. As you can see, the CPU usage is nearly the same whereas the execution time is drastically reduced. strace also shows that this time cf-agent nearly passes no time on the I/O on the tcdb databases whereas before it could block a few milliseconds at each call (and there were quite a lot of them…)

Ces résultats sont obtenus sur une machine “calme”, la différence augmente exponentiellement avec l’utilisation des entrées / sorties sur la machine, spécialement si vous atteignez les limites de votre média de stockage fixe. Comme vous pouvez le constater, l’utilisation CPU est presque la même alors que le temps d’exécution est grandement réduit. strace indique cette fois que cf-agent ne passe presque plus de temps sur les entrées / sorties des bases tcdb alors qu’avant il y passait quelques millisecondes a chaque appel (et il y en avait une quantité non négligeable).

Maintenant, si vous êtes satisfaits du résultat et souhaitez conserver le RAMdisk sur la machine, une simple entrée a la fin du fstab peut s’en charger:

# Tmpfs for the CFEngine state backend storage directory
tmpfs		/var/cfengine/state	tmpfs	size=128M,nr_inodes=2k,mode=0700,noexec,nosuid,noatime,nodiratime	0	0

Voila, ça a résolu le souci pour moi, et je pense que le rapport  avantages / inconvénients est plutôt avantageux 🙂 Est-ce que quelqu’un a déja essayé une autre solution ? (peut être plus élégante).