Optimiser les performances et les fonctionnalités de son instance StatusNet

Jean Baptiste FAVRE

Juillet 2011

English version is available here: Optimize you StatusNet instance for better performances and features

Dans l'épisode précédent

Cette publication constitue le second épisode de mon "feuilleton de l'été" consacré à StatusNet. Vous pouvez consulter les précédents là:

Je ferai appel, sans entrer dans les détails, à des éléments de configuration déjà abordés:

Néanmoins, les éléments purement liés à la configuration de StatusNet sont applicables, même si vous utilisez un autre serveur web.

Introduction

Si vous avez installé votre propre instance StatusNet, vous avez dû vous rendre compte... que c'est lent.

Par défaut, StatusNet va réaliser toutes les actions par l'intermédiaire de la connexion de votre navigateur. Par exemple, abonnements, publications, etc... se font de manière synchrone.

Cela signifie que vous risquez, si ce n'est déjà fait, d'être confrontés à des timeout sur vos requêtes, générant une erreur du serveur et un état instable de votre instance (abonnement incomplet, publication pas ou partiellement propagée, etc...). Bref, il faut remédier au problème.

Fort heureusement, StatusNet vous permet de paramétrer très finement son comportement. Petit tour d'horizon.

Faire le tri dans les plugins

StatusNet a, lors de son installation, activé un certain nombre de plugins. Malheureusement, ce point n'est à ma connaissance pas (ou alors mal) documenté. Après quelque recherches, vous tombez sur //identi.ca/main/version. Et comme le logiciel est le même, vous disposez de cette même page sur votre instance.

Par défaut donc, un certain nombre de plugins sont activés. Pour ma part, je ne les trouve pas tous utiles ou pertinents. Qu'à cela ne tienne, on peut les désactiver dans le fichier de configuration:

cat /var/www/apps/statusnet/docroot/config.php
unset($config['plugins']['default']['Mapstraction']);
unset($config['plugins']['default']['OpenID']);
unset($config['plugins']['default']['RSSCloud']);
unset($config['plugins']['default']['WikiHashtags']);
unset($config['plugins']['default']['TightUrl']);
unset($config['plugins']['default']['SimpleUrl']);
unset($config['plugins']['default']['PtitUrl']);
unset($config['plugins']['default']['Geonames']);

En fonction des plugins, vous gagnerez soit en requêtes externes (ex: GeoNames) soit en temps de calcul (ex: RSSCloud) soit en temps d'affichage de la page web (ex: Mapstraction). Dans tous les cas, vous êtes gagnant :)

Utiliser les files d'attentes asynchrones

Comme je le soulignais en introduction, le comportement par défaut de StatusNet consiste à gérer les tâches de manière synchrone. Simple, mais pas particulièrement efficace.

Par exemple, si vous devez diffuser un message à différents abonnés répartis sur d'autres instances StatusNet, il y a toutes les chances du monde pour que le boulot ne soit fait qu'à moitié lorsque la connexion sera coupée par l'expiration du temps d'exécution du script PHP.

Évidemment, rien ne vous empêche d'augmenter le temps maximum d'exécution des requêtes PHP, mais cela risque fort de causer d'autres problèmes en cascade, comme une saturation du serveur web. La vraie solution, la seule pérenne en tout cas, consiste à activer le gestionnaire de file d'attente intégré de manière à permettre l'exécution de certaines tâches de manière asynchrone.

Inconvénient de cette solution: il faut alors gérer un service, écrit en PHP, qu'il faudra démarrer à l'allumage du serveur et redémarrer au besoin ce qui signifie avoir un accès en ligne de commande au serveur. Ce seul point disqualifie la plupart des offres d'hébergement mutualisé pour mettre en place sa propre instance StatusNet.

L'activation des files d'attente se fait de manière relativement simple:

cat /var/www/apps/statusnet/docroot/config.php
$config['queue']['enabled'] = true;
$config['daemon']['piddir'] = '/var/www/apps/statusnet/tmp/';
$config['daemon']['user'] = 'statusnet';
$config['daemon']['group'] = 'statusnet';

MySQL sera utilisé en tant que backend par défaut. Pour d'évidentes raisons liées à la sécurité, nous spécifions un utilisateur particulier, et non privilégié, pour faire fonctionner tout ça, en l'occurence l'utilisateur que PHP-FPM utilise pour faire fonctionner StatusNet.

Vous pouvez alors démarrer le service. Dans la mesure où il faudra réaliser un changement d'ID utilisateur, il faut avoir les droits root pour pouvoir lancer le script:

Démarrage du service
/usr/bin/sudo /bin/sh /var/www/apps/statusnet/docroot/scripts/startdaemon.sh

Le script va d'abord lister les services pertinents compte tenu de la configuration de StatusNet et les démarrer les uns à la suite des autres. Par défaut il n'y en a qu'un: queuedaemon.php. Vous pouvez d'ailleurs le vérifier à la main:

Liste des services qui seront démarrés par le script startdaemon.sh
/usr/bin/php /var/www/apps/statusnet/docroot/scripts/getvaliddaemons.php
/var/www/apps/statusnet/docroot/scripts/queuedaemon.php

Pour stopper les process, un autre script est disponible:

Arrêt du service
/usr/bin/sudo /bin/sh /var/www/apps/statusnet/docroot/scripts/stopdaemon.sh

Pour paramétrer le démarrage de tout ça au boot, on peut ajouter la commande start dans le fichier /etc/rc.local:

cat /etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Start status.net daemons
/bin/sh /home/hosting/www/apps/status/docroot/scripts/startdaemons.sh 

exit 0

À partir de maintenant, seuls les abonnements seront gérés de manière synchrone. La réception et l'envoi des messages s'effectuera, quant à elle, de manière asynchrone.

Accessoirement, le jour où, par exemple, identi.ca rencontre quelques soucis, le gestionnaire de file d'attente fera le boulot à votre place: il ré-essaiera de délivrer les messages éventuels tout seul comme un grand (le nombre d'essai maximum est fixé à 10 par défaut).

Utiliser les fonctionnalités de cache d'opcode

Un grand classique dans le monde PHP. Dans certains cas, PHP va, presque, prendre plus de temps pour analyser et compiler le code que pour l'exécuter.

Je ne sais pas si c'est exactement le cas ici, mais, clairement, l'utilisation de l'extension APC accélère votre instance. Alors, pas d'hésitation, on fait:

Installation du module APC pour PHP
aptitude install php5-apc
cat /etc/php5/fpm/conf.d/apc.ini
; configuration for php apc module
extension=apc.so

D'après mon expérience, un segment mémoire de 32M (configuration par défaut de l'extension PHP) suffit amplement pour StatusNet.

Attention toutefois à une chose: même avec PHP-FPM et des processus utilisant des uid/gid différents, le cache APC est partagé entre tous les processus. Normalement pas de soucis, mais c'est toujours bon à savoir.

Utiliser les fonctionnalités de cache de données

StatusNet supporte plusieurs systèmes de cache de données : APC, Memcache, Memcached, Xcache, ... Je n'en retiendrai que 2, les 2 que je connais le mieux: APC et Memcache

En fait, il est inutile d'utiliser les plugins APC et Memcache de StatusNet en même temps. Ils font exactement le même boulot: cacher des informations diverses, dont la configuration. En l'occurrence, le premier déclaré dans le fichier de configuration gagne.

La logique voudrait alors que l'on privilégie APC au détriment de Memcache car cela évite des connections TCP. Oui mais... le cache APC est propre à chaque processus. Cela signifie que les processus PHP-FPM vont utiliser un segment APC, et les services de file d'attente que l'on a activé au-dessus un autre.

Résultat: les caches vont être désynchronisés. Effet le plus visible: la timeline n'est plus rafraîchie, même si les nouveaux messages sont bien enregistrés dans le système. Génant.

Je préconise donc d'utiliser le plugin Memcache, qui sera partagé par le moteur StatusNet d'une part et par les services de file d'attente d'autre part.

Installation du module Memcache pour PHP
aptitude install php5-memcache
cat /etc/php5/fpm/conf.d/memcache.ini

; configuration for php apc module
extension=memcache.so

Notez que StatusNet supporte aussi l'extension Memcached via le plugin éponyme.

Il ne reste plus qu'à activer le plugin Memcache dans le fichier de configuration StatusNet:

cat /var/www/apps/statusnet/docroot/config.php
addPlugin('Memcache');

Bien entendu, il faut également, si ce n'est déjà fait, installer le serveur memcache:

Installation du serveur memcache
aptitude install memcache

Enfin, il faut redémarrer PHP-FPM de manière à prendre en compte les changements:

Redémarrage de l'application
/etc/init.d/php-fpm restart

Vous devriez déjà sentir la différence... non ? Alors rajoutons-en une couche. Un plugin méconnu de StatusNet utilise encore un peu plus les fonctionnalités de cache: InProcessCache

Malheureusement, peu d'informations sont disponibles sur son fonctionnement, sauf à se plonger dans le code, ce que je vous laisse faire. Une information importante néanmoins:

cat /var/www/apps/statusnet/docroot/plugins/InProcessCache.php
/**
 * Extra level of caching
 *
 * This plugin adds an extra level of in-process caching to any regular
 * cache system like APC, XCache, or Memcache.
 * 
 * Note that since most caching plugins return false for StartCache*
 * methods, you should add this plugin before them, i.e.
 *
 *     addPlugin('InProcessCache');
 *     addPlugin('XCache');

Dont acte. La configuration devient:

cat /var/www/apps/statusnet/docroot/config.php
addPlugin('InProcessCache');
addPlugin('Memcache');

Il existe encore d'autres plugins permettant la mise en cache des informations. Je vous laisse creuser ceux qui vous intéressent, ils sont tous dans le répertoire /var/www/apps/statusnet/docroot/plugins.

Attention cependant, certains ne cohabitent pas bien ensembles, notamment à cause des extensions PHP qui leurs sont nécessaires (exemple: APC et XCache).

Merci à Karlesnine qui m'a permis de piger comment fonctionnaient les plugins APC et Memcache :)

Checkschema m'a tué

Checkschema est bien l'une des fonctionnalités de StatusNet dont le paramétrage par défaut m'a le plus surpris. Il s'agit ni plus ni moins de vérifier la structure de la base de données.

Les plugins peuvent avoir besoin de créer une ou plusieurs tables spécifiques, et checkschema se revèle alors extrèmement pratique puisqu'il vérifie à votre place que le schéma de la base est conforme à la configuration. Merveilleux lorsque l'on découvre StatusNet.

Malheureusement, le paramétrage par défaut effectue cette vérification... à chaque requête HTTP. Une véritable catastrophe.

Vite, changeons cela:

cat /var/www/apps/statusnet/docroot/config.php
$config['db']['schemacheck'] = 'script';
$config['db']['database'] = 'mysqli://statusnet:statusnet_password@unix(/var/run/mysqld/mysqld.sock)/statusnet';

Au passage, j'en ai profité pour changer la manière de se connecter à la base de données. Lorsque PHP et MySQL sont sur le même serveur, utilisez toujours les sockets de préférence à la connection TCP.

Une fois checkschema désactivé, n'oubliez pas de vérifier manuellement la structure de la base de données si vous changez la configuration. Pour cela, vous pouvez utiliser le script prévu à cet effet.

Vérification manuelle de la structure de la base de données
/usr/bin/php /var/www/apps/statusnet/docroot/scripts/checkschema.php

Je n'ai toujours pas compris pourquoi checkschema est paramétré de la sorte par défaut sans plus de précautions. Toujours est-il que c'est là que j'ai eu le plus gros gain de performances, en tout cas l'un des plus visibles :-/

Alléger l'interface web

Après avoir tenté d'optimiser le cœur du système, passons à l'interface utilisateur, en l'occurrence l'interface web.

Vous n'aurez pas grand contrôle sur la structure même des pages web. De plus, elles sont relativement complexes. Par exemple, la page de votre profil affiche les avatars de vos abonnements. Autant de connexions HTTP supplémentaires à réaliser pour les récupérer.

Il existe néanmoins une solution qui, à défaut d'être super efficace, améliore un peu les choses: changer le thème de l'interface pour un autre plus léger, et surtout choisir un domaine alternatif pour la diffusion des fichiers statiques.

cat /var/www/apps/statusnet/docroot/config.php
$config['site']['theme'] = 'cleaner';

/* Themes location configuration */
$config['theme']['server']         = 'static.domain.tld';
$config['theme']['path']           = '/status/theme/';
$config['theme']['dir']            = '/var/www/domain/domain.tld/docroot/static/status/theme';

/* Javascript location configuration */
$config['javascript']['server'] = 'static.domain.tld';
$config['javascript']['dir'] = '/var/www/domain/domain.tld/docroot/static/status/js';
$config['javascript']['path'] = 'status/js/';

/* Avatar location configuration */
$config['avatar']['server'] = 'static.domain.tld';
$config['avatar']['dir']    = '/var/www/domain/domain.tld/docroot/static/status/avatar/';
$config['avatar']['path']   = '/status/avatar/';

/* Backgrounds location configuration */
$config['background']['server'] = 'static.domain.tld';
$config['background']['dir']    = '/var/www/domain/domain.tld/docroot/static/status/background/';
$config['background']['path']   = '/status/background/';

/* Attachments location configuration */
$config['attachments']['server'] = 'static.domain.tld';
$config['attachments']['dir']    = '/var/www/domain/domain.tld/docroot/static/status/file/';
$config['attachments']['path']   = '/status/file/';

/* Plugins static files location configuration */
$config['plugins']['server'] = 'static.domain.tld';
$config['plugins']['path']   = 'status/plugins';

Naturellement, il faut du coup configurer un hôte virtuel dédié. Pour quoi dédié ? Tout simplement car je désactive par défaut les journaux d'accès pour les ressources statiques, ce qui élimine les opérations d'entrées/sorties disque correspondantes:

cat /etc/nginx/site-enabled/static.domain.tld
server {
    listen   80;
    server_name  static.domain.tld;
    location / {
        root   /var/www/domains/domain.tld/docroot/static;
    }
	access_log off;
	error_log /dev/null crit;
	log_not_found off;
}

Je vous laisse à titre d'exercice le soin de séparer les fichiers PHP des fichiers statiques. En effet, il n'est pas concevable de laisser les 2 hôtes virtuels utiliser la même arborescence, sauf à vouloir que votre fichier config.php soit librement accessible via l'hôte virtuel static.domain.tld.

Cette étape de l'optimisation aura été l'occasion de vous montrer jusqu'à quel point on peut configurer une instance StatusNet.

Désactiver le support des SMS

Destiné au départ à "copier" Twitter, StatusNet intègre lui aussi le support des SMS. On peut bien entendu le désactiver:

cat /var/www/apps/statusnet/docroot/config.php
$config['sms']['enabled'] = false;

Une petite astuce dont la découverte m'a ravi tant les processus d'abonnements étaient devenus pénibles: il fallait à chaque fois décocher l'option SMS.

Comment faire pour trouver les options de configuration

Pas de secret, tout est dans le code :)

Plus sérieusement, il y a 2 fichiers que vous devez connaître. Le premier contient les valeurs de configuration par défaut que le système utilisera à moins qu'elles ne soient surchargées:

cat /var/www/apps/statusnet/docroot/lib/default.php
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Default settings for core configuration
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <//www.gnu.org/licenses/>.
 *
 * @category  Config
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2008-9 StatusNet, Inc.
 * @license   //www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      //status.net/
 */

$default =
  array('site' =>
        array('name' => 'Just another StatusNet microblog',
              'nickname' => 'statusnet',
[...]

Le second, lui, vous donne toutes les options de configurations actives et leur valeur courante:

cat /var/www/apps/statusnet/docroot/lib/default.php
/usr/bin/php /var/www/apps/statusnet/docroot/scripts/setconfig -a
site                 name                 'Yet another StatusNet install'
site                 nickname             'status.domain.tld'
site                 wildcard             NULL
[...]

Cette commande est doublement indispensable: tout d'abord elle sera votre compagnon lors des soirées que vous passerez à hacker StatusNet. D'autre part, elle vous sera nécessaire si vous décidez de poser des questions sur le forum StatusNet. À conserver précieusement donc :)

Conclusion

Voici le fichier de configuration tel qu'il devrait être si vous avez appliqué toutes les recettes que j'ai détaillées:

cat /var/www/apps/statusnet/docroot/config.php
<?php
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }

/* Core configuration */
$config['site']['name'] = 'Yet another StatusNet instance';
$config['site']['server'] = 'status.domain.tld';
$config['site']['path'] = '';

/* SMS support configuration */
$config['sms']['enabled'] = false;

/* Database connection */
$config['db']['database'] = 'mysqli://statusnet:statusnet_password@unix(/var/run/mysqld/mysqld.sock)/statusnet';
$config['db']['type'] = 'mysql';

/* Queue & daemon control options */
$config['queue']['enabled'] = true;
$config['daemon']['piddir'] = '/var/www/apps/statusnet/tmp/';
$config['daemon']['user'] = 'statusnet';
$config['daemon']['group'] = 'statusnet';

/* UI configuration */
$config['site']['theme'] = 'cleaner';

/* Javascript location configuration */
$config['javascript']['server'] = 'static.domain.tld';
$config['javascript']['dir']    = '/var/www/domains/domain.tld/docroot/static/status/js';
$config['javascript']['path']   = 'status/js/';

/* Themes location configuration */
$config['theme']['server'] = 'static.domain.tld';
$config['theme']['dir']    = '/var/www/domains/domain.tld/docroot/static/status/theme/';
$config['theme']['path']   = '/status/theme/';

/* Avatar location configuration */
$config['avatar']['server'] = 'static.domain.tld';
$config['avatar']['dir']    = '/var/www/domains/domain.tld/docroot/static/status/avatar/';
$config['avatar']['path']   = '/status/avatar/';

/* Backgrounds location configuration */
$config['background']['server'] = 'static.domain.tld';
$config['background']['dir']    = '/var/www/domains/domain.tld/docroot/static/status/background/';
$config['background']['path']   = '/status/background/';

/* Attachments location configuration */
$config['attachments']['server'] = 'static.domain.tld';
$config['attachments']['dir']    = '/var/www/domains/domain.tld/docroot/static/status/file/';
$config['attachments']['path']   = '/status/file/';

/* Plugins static files location configuration */
$config['plugins']['server'] = 'static.domain.tld';
$config['plugins']['path']   = 'status/plugins';

/* Plugins configuration */
unset($config['plugins']['default']['Mapstraction']);
unset($config['plugins']['default']['OpenID']);
unset($config['plugins']['default']['RSSCloud']);
unset($config['plugins']['default']['WikiHashtags']);
unset($config['plugins']['default']['TightUrl']);
unset($config['plugins']['default']['SimpleUrl']);
unset($config['plugins']['default']['PtitUrl']);
addPlugin('InProcessCache');
addPlugin('Memcache');

Voilà, nous arrivons au terme du second épisode. La prochaine fois, petite récréation, nous verrons comment interconnecter StatusNet et Twitter et obtenir l'affichage des nouveaux messages en temps réel (ou presque).

Enjoy :)

Sources et références

StatusNet

StatusNet
  • //status.net/open-source/
  • //status.net/wiki/
Installation
  • //status.net/wiki/Installation
Getting started
  • //status.net/wiki/Take_a_tour
  • //status.net/wiki/NoticeSymbols

NGinx

Official website
  • //www.nginx.org/
  • ////wiki.nginx.org/

PHP-FPM

Official website
  • //www.php-fpm.org
  • //fr2.php.net/manual/en/install.fpm.php

À propos de Jean Baptiste FAVRE

Je passe le plus clair de mon temps libre sur Internet à travailler sur GNU/Linux avec Debian ou CentOS, la virtualisation avec Xen et KVM et les technologies cluster avec Corosync et OpenAIS. Particulièrement intéressé par Linux, Netfilter, la virtualisation, le monitoring et les clusters, la plupart de mes travaux personnels sont publiés sur ce site et les autres ne sauraient tarder.
A titre professionnel, j'administre des serveurs sous RedHat ou CentOS ainsi qu'une ferme de serveurs VMware ESXi version 4.
De temps, à autre, je parviens à lâcher mon clavier pour lire un bon bouquin tout en écoutant de la musique, mais ça ne dure jamais longtemps.

License

Creative Commons License Cette publication est publiée sous contrat Creative Common by-nc-sa

Valid XHTML 1.0 Strict |  Valid CSS |  contrat Creative Common by-nc-sa

Table des matières

  1. Dans l'épisode précédent
  2. Introduction
  3. Faire le tri dans les plugins
  4. Utiliser les files d'attentes asynchrones
  5. Utiliser les fonctionnalités de cache d'opcode
  6. Utiliser les fonctionnalités de cache de données
  7. Checkschema m'a tué
  8. Alléger l'interface web
  9. Désactiver le support des SMS
  10. Comment faire pour trouver les options de configurations
  11. Conclusion
  12. Sources and references
  13. About ...
  14. License