Bug des sessions PHP sous Debian/Ubuntu

Je suis tombé sur un truc un peu étrange récemment, sur mes serveurs de production.

Au moment de faire une mise en production, notre programme de déploiement a remonté une erreur inhabituelle. En regardant de plus près, je me suis rendu compte qu’il n’avait pas pu écrire sur le disque les fichiers correspondant aux nouvelles versions des projets. Pourtant, il restait encore énormément de place sur le disque dur (à peine 15% d’espace disque utilisé).
Le soucis venait du fait que tous les inodes du système de fichiers avaient été consommés. Wow, la vache. Avoir bouffé tous les inodes, alors que 85% d’espace disque est encore libre, ça voulait dire qu’un nombre incroyable de minuscules fichiers avait été créé.

Le premier réflexe est de se demander quel bout de code on a bien pu écrire, qui génère cette merde. Et pour analyser l’origine du problème, la première étape est déjà d’essayer de savoir dans quel répertoire ces fichiers ont été créés. Donc on a fait un script pour compter le nombre de fichiers dans chaque répertoire. La fête, quoi.

On a découvert qu’il y avait plus de 16 millions de fichiers dans le répertoire /var/lib/php5. Attendez… qu’est-ce qui peut bien avoir créé autant de fichiers dans ce répertoire ?
Pas besoin de chercher très longtemps pour s’apercevoir qu’il s’agit de l’endroit où PHP écrit les fichiers servant à stocker les sessions utilisateurs.

Mais normalement, les sessions sont effacées au bout d’un certain temps. Tout le monde sait ça. C’est d’autant plus étonnant que notre framework Temma utilise son propre système de sessions (stockées dans Memcache), et que donc nous ne devrions pas créer ces fichiers car les sessions PHP sont contournées.
J’ai commencé par essayer de comprendre pourquoi les fichiers de sessions n’ont pas été effacés après un certain délai. Ce comportement est géré dans le fichier de configuration de PHP, par les directives session.gc_maxlifetime, session.gc_probability et session.gc_divisor. Pour faire simple, une installation standard sous Ubuntu (et, semble-t-il, aussi sous Debian) prévoit que les sessions aient une durée de 24 minutes.

Par contre, il existe deux manières différentes de faire le ménage dans les sessions.
La première est de configurer PHP pour qu’à chaque requête entrante, il se donne une chance de faire le ménage parmi les sessions ouvertes. Cela est calculé avec les paramètres que j’ai cités juste avant. Par exemple, le ménage dans les sessions pourra être fait toutes les mille requêtes (ce qui veut dire qu’une requête sur mille sera ralentie par ce traitement supplémentaire).
La seconde manière est de faire tourner régulièrement un script, en le lançant par crontab, dont le rôle est de faire le ménage pour effacer les fichiers correspondant aux sessions qui sont arrivées à expiration.

Sur Ubuntu (et donc sûrement sur Debian aussi), c’est la seconde solution qui a été choisie. Mais le truc complètement incompréhensible, c’est que le script lancé par crontab est commenté !
Pour être exact, il s’agit du fichier /etc/cron.d/php5. Il ne contient qu’une seule ligne, dont la commande est censée s’exécuter toutes les demi-heures. Mais comme je le disais, cette ligne est commentée. Donc le ménage n’est jamais fait.

Dans notre cas, le truc particulièrement con, c’est que les sessions PHP n’auraient pas dû être utilisées. Comme je l’ai dit plus haut, notre framework possède son propre système de session. Le hic, c’est que lorsque j’ai ouvert ce framework sous licence libre, j’ai ajouté une petite évolution : C’est bien beau de gérer nos sessions dans Memcache, mais cette solution ne peut pas satisfaire tout le monde. Donc il possède un fallback qui l’amène à utiliser les sessions PHP si les sessions n’ont pas explicitement été configurées pour utiliser le cache.

Partant de là, le code qui s’occupe de nos médias (principalement l’affichage des images sur nos sites) a connu un petit bug de configuration, qui activait les sessions. Cela ne sert à rien, on est d’accord ; il n’y a pas besoin de session utilisateur pour servir une image.
Mais comme il n’était pas prévu que les sessions soient activées, elles n’étaient évidemment pas configurées pour être stockées en cache. Donc elles se sont retrouvées à passer par le mécanisme des sessions PHP.
C’est ainsi que nous nous sommes retrouvés à écrire des fichiers sur le disque dur pour chaque visiteur de passage sur nos sites…

Alors si jamais vous vous retrouvez à cours d’inode, commencez par regarder du côté des sessions, on ne sait jamais.

10 commentaires pour “Bug des sessions PHP sous Debian/Ubuntu

  1. Voila qui est intéressant, il y a un an je suis tombé sur le même problème. Le serveur en question été un Debian mais comme sur ton Ubuntu la crontab été désactivé. A l’époque, moi et mon collègue avions incriminé ISP Config (un panel de gestion de serveur web), mais visiblement le problème été plus en amont dans le système.

    Merci pour l’info.

  2. Effectivement, je n’utilise pas de panel (ni ISPConfig, ni Webmin, ni Plesk, …), donc l’origine du problème est bien la configuration de base du système.
    Content de savoir que je ne suis pas le seul à m’être cassé les dents dessus 😉

  3. Salut Amaury.

    Le problème vient effectivement du script en crontab que nous avons commenté, parce que ce dernier ralentissait affreusement les serveurs de la boite où tu travailles.

    Sur Ubuntu, lorsque tu installes PHP, une entrée en cron est créée pour supprimer les sessions.
    Or, il se trouve que le script chargé des supprimer ces sessions mettait beaucoup de temps (plusieurs jours) pour terminer sa tâche. Cela engendrait l’exécution plusieurs instances du même script en même temps; ce qui ralentit les performances des nos serveurs.

    Bonne journée

  4. Merci pour l’info, Lotfi. Sincèrement, je ne me souvenais pas que tu avais commenté ça ; surtout que j’ai trouvé pas mal de forum où les mecs s’étonnaient de trouver la commande commentée chez eux aussi.

    Pour le coup, je suis étonné qu’on ait commenté ça parce qu’on trouvait ça trop lent. Dans tous les cas c’était un mauvais choix (ou au moins une mauvaise analyse), parce que maintenant je me retrouve avec des serveurs qui contiennent 16 millions de fichiers inutiles. Et comme ça prend du temps pour modifier un répertoire qui contient autant de fichiers, l’effacement se fait au rythme éblouissant d’environ 1 fichier par seconde. 16 millions de secondes, ça fait plus de 6 mois…

    Ce qui est vraiment étrange, c’est que cette ligne est aussi commentée sur des serveurs qu’on a réinstallé depuis ton départ. Je n’y comprends plus rien.

  5. De ce que j’ai pu voir les paquets officiels ubuntu ne diposent pas de commentaire sur ce script de cron.
    Certains hébergeurs utilisent leur propre dépôt pour les distributions cela vient peut-être de cela.

    Pour vérifier le contenu de ce script il suffit de regarder dans le php5-common_5.xxxxxx.deb

  6. Je viens de vérifier sur mes serveur Ubuntu (12.04 LTS) la ligne n’est pas commenté (install de base OVH)

  7. @Greg : OK, merci d’avoir vérifié.

    J’imagine que le commentaire de Lotfi était juste, alors. On l’avait commenté par erreur (oui, c’est une erreur, il aurait fallu faire autrement). Et donc mon article ne sert pas à grand-chose, si ce n’est montrer une démarche possible d’investigation sur ce genre de problème IT.

    Ce qui m’étonne, c’est que j’ai vraiment vu des gens dans la même situation, sur plusieurs forums. Évidemment, je n’ai pas gardé les liens…

  8. Ça m’étonnait aussi que cette commande de crontab soit décommentée par défaut sous une distrib debian/ubuntu. Article intéressant, tout retour d’expériance est bon à prendre !

  9. Merci beaucoup, moi j’ai eu le même soucis, mais les sessions s’enregistrait dans /tmp, donc pareil des millions de fichiers….

    Ça m’empêchais même de démarrer le serveur.

    La tâche cron est bien lancé, mais ne supprime pas dans le bon répertoire…… (/var/lib)

    Au redémarrage, je vais changer cela….

    Une journée de perdue et une.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Notifiez-moi des commentaires à venir via email. Vous pouvez aussi vous abonner sans commenter.