Il y a quelque temps je me suis offert un SSD pour redonner un coup de jeune à ma configuration. J’essaye d’en prendre soin. J’ai suivi les recommandations que j’ai trouvé sur internet afin de maximiser la durée de vie du disque. Minimiser les écritures est un des conseils qui revient le plus souvent, j’ai donc essayé de faire la chasse aux accès disque inutile…
Un des moments où j’entendais vraiment gratter mon disque mécanique était lorsque je lançais des tests d’intégration. Beaucoup de lectures / écritures de la base de données étaient faites à ce moment là. L’intégralité de mes tests (230 scénarios et 1814 steps Cucumber) se lançait en plus ou moins 15 minutes. Aujourd’hui la même suite de tests se lance en plus ou moins 35 secondes. Dans cet article, je vous explique comment s’est passé la transition.
Mon projet utilise PostgeSQL 9 et ma machine tourne sous la dernière Ubuntu. En conséquence, les infos que vous trouverez ici s’appliquent principalement à cette configuration. Le principe peut à mon avis s’appliquer avec d’autres SGBD.
Pour cet article je suppose que vous avez déjà une installation de PostgreSQL fonctionnelle. Dans mon cas il s’agit de l’installation par défaut qui est réalisée lors de l’installation du paquet Ubuntu. Je suppose aussi que vous avez quelques centaines de méga-octets de mémoire vive qui ne sont jamais utilisés.
Avant toute chose, comprenez bien qu’après cette étape votre machine hébergera deux instances de PostgreSQL. Votre instance
habituelle, probablement nommée main
, ainsi qu’une nouvelle instance nommée test
que nous allons créer maintenant.
L’intérêt d’avoir deux instances est que l’on va pouvoir configurer l’une indifféremment de l’autre. Il ne faut pas confondre ces instances avec les bases de données en elles même. Plusieurs bases sont souvent gérées par une même instance, dans ces cas là toutes les bases partagent des options communes.
Les commandes suivantes vous permettront de créer une nouvelle instance :
root # su postgres
postgres $ pg_createcluster --port=5434 9.3 test
Une instance de PostgreSQL sera créée, les fichiers relatifs à la configuration de l’instance seront présents dans le répertoire
/etc/postgresql/9.3/test
et les données seront dans le répertoire /var/lib/postgresql/9.3/test
.
Étant un peu laxiste sur la sécurité d’un environnement de développement et encore plus lorsqu’il s’agit d’une base utilisée
pour les tests, je configure les permissions d’accès à PostgreSQL via le fichier /etc/postgresql/9.3/pg_hba.conf
comme ceci :
local all postgres trust
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 md5
Cette configuration, on le verra plus tard, va nous permettre de nous connecter à PostgreSQL en tant qu’utilisateur postgres
et sans mot de passe.
L’astuce repose entièrement sur la capacité du système d’exploitation à créer un système de fichiers résidant en mémoire vive.
Pour créer un tel système de fichiers, on modifie le fichiers /etc/fstab
en ajoutant la ligne suivante :
tmpfs /media/tmpfs tmpfs defaults,size=1g 0 0
Cette ligne aura pour effet de réserver 1Go de mémoire vive qui sera mise à disposition sous forme de système de fichiers sur
le point de montage /media/tmps
. Il faudra créer ce répertoire à la main avec la commande : sudo mkdir -p /media/tmpfs
.
Le système de fichiers sera disponible au prochain redémarrage. Pour le rendre disponible dès maintenant, utilisez la commande
sudo mount /media/tmpfs
.
Ce système de fichiers sera vidé à chaque redémarrage de la machine. À chaque démarrage de la machine, on va donc charger
les données de notre instance test
dans le système de fichiers en RAM. Pour cela on peut utiliser le fichiers /etc/rc.local
qui est exécuté au lancement du système.
IN_MEMORY_PG=/media/tmpfs/postgres/9.3/
mkdir -p $IN_MEMORY_PG
cp -R /var/lib/postgresql/9.3/test $IN_MEMORY_PG
chmod -R 700 $IN_MEMORY_PG
chown -R postgres $IN_MEMORY_PG
exit 0
À ce moment, vous pouvez redémarrer votre machine où bien simplement exécuter les commandes ci-dessus. Ces commandes auront
pour résultat de copier les données du cluster test
vers notre système de fichiers en mémoire vive. Vous aurez peut être à
adapter la version de PostgreSQL, le nom du cluster ou autre en fonction de votre distribution.
Une fois la copie des fichiers faite, on modifie la configuration de PostgreSQL pour utiliser le système de fichiers. Chez moi,
cela se passe dans le fichier /etc/postgresql/9.3/test/postgresql.conf
où il faut modifier la valeur de l’option data_directory
.
data_directory = '/media/tmpfs/postgres/9.3/test'
Une fois cette option modifiée, vous pouvez redémarrer PostgreSQL avec la commande sudo service postgresql restart
. À présent,
votre base est prête et devrait être beaucoup plus rapide ! À chaque démarrage, la base sera vide.
La configuration de votre application, dans son environnement de test doit être légèrement différente…
L’utilisateur utilisé doit être postgres
, le port doit être le 5434
et il faut s’assurer qu’avant chaque
lancement, la base de données existe et qu’elle contient bien le schéma requis.
Dans mon application, j’ai utilisé le code suivant pour qu’en environnement de test, on tente de créer la base,
de charger les extensions nécessaire (ici hstore
) et de migrer le schema.
# In test mode try the connection differs
if ENV['RACK_ENV'] == 'test'
# Create
%x{createdb -U #{user} -p #{port} #{name}}
DB = Sequel.connect(url)
# Load DB extensions
%w(hstore).each do |ext|
DB.run "create extension if not exists #{ext};"
end
# Migrate
Sequel.extension :migration
Sequel::Migrator.run(DB, SEQUEL_MIGRATION_DIR)
else
DB = Sequel.connect(url)
end
C’est le seul changement que j’ai apporté à mon application !
On peut très bien décider de persister le données de l’instance test
avant l’extinction de la machine en utilisant un script à l’extinction.
Pour faire ça on va copier les données depuis le système de fichiers en RAM vers le disque à l’exact inverse de ce qui se fait
au démarrage.
Une architecture logicielle ingénieuse permet de diminuer les échanges avec la base de données. La couche de persistance doit idéalement être indépendante du reste de la logique de l’application.
On est donc en droit de se dire que cette optimisation n’est pas une nécessité. Si vous utilisez Rails et ActiveRecord, il y a de forte chance pour que la persistance soit au cœur votre application et que cette dernière en dépende totalement…
Dans le cas des tests d’intégration, on provoque des échanges avec la base de données de manière volontaire, c’est tout le principe.
Cette astuce ne s’appliquera pas forcément à tous vos projets, ni à toutes les configurations matérielles. Toutefois, si vous avez l’occasion de l’appliquer, je vous le recommande très vivement. Dans mon cas mes tests ont été 25 fois plus rapides, c’est tout ce que je vous souhaite.
L’équipe Synbioz.
Libres d’être ensemble.