Optimize your own StatusNet instance for better performances and features

Jean Baptiste FAVRE

2011 July

La version française est disponible ici: Optimiser les performances et les fonctionnalités de son instance StatusNet

Previously about StatusNet

This is the second part of my StatusNet serie. Previous parts are available here:

I'll use some configurations I already wrote about here:

Anyway, you should be able to use all purely StatusNet related configuration, whatever you web server could be.

Introduction

If you have installed your own StatusNet instance, you may have noticed that's… it's slow.

By default, all actions are processed in synchronous mode. As an exemple, subscriptions, publications, etc… are done within the same HTTP connection.

That means you may face some timeout mostly because of PHP max execution time value. The result is that your instance may not be fully consistent: half-staged subscriptions, publications not completely propagated, …

Hopefully, StatusNet allows you to fine-tune its configuration. Let's see how to do it.

Disable unused plugins

When installed, some plugins are enabled by default. Unfortunatly, this point is, as far as I've seen, not well documented. After some search, you find //identi.ca/main/version. Since you use the same software, you have the same URL on your instance.

All default-enabled plugins are not always usefull, at least from my point of vue. You can disable them in your config.php file, like this:

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']);

Depending on with plugin you consider, you will save some external requests (ex: GeoNames), some compute time (ex: RSSCloud) or web page load time (ex: Mapstraction).

Use asynchronous queue daemons

As I said in introduction, default StatusNet behaviour is to perform all tasks in a synchronous manner. Very simple, but not so optimized.

Let's say you have to publish a dent to all you followers spreaded accross identi.ca and many other standalone instances. That means your instance will connect to each other instances one time to deliver your dent. In a synchronous way, there are chances that the request ends with timeout without having dent delivered everywhere.

Of course, you still can increase PHP max execution time, but it's not really a solution since your web server will be more busy. The only suitable solution here is to have the job done as background job. That's exactly what queue are done for.

The drawback of this is you'll have to deal with a PHP daemon. you'll have to start the daemon at server boot eand be able to restart it in case of failure. That also means a shell access on the server which prevent you using StatusNet on most of the shared hosting platform.

Queue activation can be easily done:

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 will be used as default backend. For obvious security reasons, we'll use a specific non privileged user for running daemon. Here, statusnet user which is used for PHP-FPM StatusNet pool.

You can then start daemon. Since we have to switch user context, you need to start it as root:

Queue daemons start
/usr/bin/sudo /bin/sh /var/www/apps/statusnet/docroot/scripts/startdaemon.sh

Script will first check which daemon have to be started. Only one will be started by default: queuedaemon.php. You can also check it manually:

Enabled daemon list used by startdaemon.sh
/usr/bin/php /var/www/apps/statusnet/docroot/scripts/getvaliddaemons.php
/var/www/apps/statusnet/docroot/scripts/queuedaemon.php

To stop daemons, you can use following command:

Daemons stop
/usr/bin/sudo /bin/sh /var/www/apps/statusnet/docroot/scripts/stopdaemon.sh

To get daemons started at server's boot, you can use /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

Starting now, only subscriptions will be processed in a synchronous way. Dents propagation will be done asynchronously.

A benefic side effect of this is resilience, one more time. Should identi.ca's API become slow, your queue manager will try delivering dents again and again (default value is 10 tries).

Use PHP opcode caching extension

If you use PHP from some times, you know it for sure. In some cases, PHP will almost spend more time parsing and compiling scripts code than executing it.

I don't exactly know if it's the case here, but anyway I felt that adding APC support to PHP gives very interesting results. To enable APC extension:

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

According to my experience, default configuration for APC extension for PHP is enough for StatusNet. That is, one memory segment of 32MB.

Just beware of one thing: despite PHP-FPM and process using differents uid/gid, APC cache is shared between processes. Basically nothing special, but it's alway good to know that sort of thing.

Use data cache feature

StatusNet does support many caching systems: APC, Memcache, Memcached, Xcache, ... I'll only talk about the 2 I know better: APC & Memcache.

Basically, both APC and Memcache plugins intent to do the same thing: cache various informations, including configuration. Practically, the first which is enabled in configuration file wins.

You could want to use APC instead of Memcache to avoid TCP connections to memcache server. Yes, but... APC cache is not shared between PHP-FPM and PHP-CLI. That means that, if you use Queue daemons, you'll have 2 caches which won't synchronized.

Most visible effect: timeline won't get refreshed, even if new notices will be saved in database. Bad...

That's why I suggest to use Memcache plugin instead of APC. This way, both queue daemons and web interface will share the same cache.

Install Memcache extension for PHP
aptitude install php5-memcache
cat /etc/php5/fpm/conf.d/memcache.ini

; configuration for php apc module
extension=memcache.so

You just have now to enable APC & Memcache support in StatusNet:

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

Of course, if not done, you must install memcache server:

Install Memcache server
aptitude install memcache

Then you have to restart PHP so that changes can be applied:

Restart application stack
/etc/init.s/php-fpm restart

Here you should see the difference… no ? OK so, let's add another cache layer. This is not a StatusNet wellknown plugin. There not so much informations about it internal behaviour but you can still have a look on source code. This plugin is called: InProcessCache

Just a important information though:

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');

Note for later, always read comments… OK so, configuration becomes:

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

There are some other plugins related to cache operations. You can check them in /var/www/apps/statusnet/docroot/plugins directory.

Just beware to the fact that some of them may conflict as related PHP extensions (just like APC and XCache).

That said, a big thank to Karlesnine who helped me in understanding APC and Memcache StatusNet plugins behaviour.

Checkschema killed me

Checkschema is one of the most surprising default feature I've met in StatusNet. Its aim is to automatically check DB structure.

Some plugins might require specific MySQL tables and in a way, checkschema will really help you making sure, in background, that all tables are created. Really awesome when you discover StatusNet. But…

But by default, theses checks will take place… at each HTTP request !

Ouch, let's fix it quickly:

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';

At the same time, I changed the way StatusNet connects to MySQL server. If you host all components ont he same server, you should always prefer sockets to TCP connections.

Of course, once checkschema has been disabled, you'll have to manually check DB structure and eventually make new tables created. For that, you should use the script checkschema.php as follow:

Manual check of DB structure
/usr/bin/php /var/www/apps/statusnet/docroot/scripts/checkschema.php

I still have no explanation about the default value accuracy for checkschema. Anyway, have it disabled was one on the most important performance increase I had with StatusNet, at least one of the most visible.

Tweaking user web interface

There are also some tips to optimize Web UI. To be honnest, you won't be really able to have full control on web page structure, there are no templates, but you still can separate domain name for static files.

As an example, in your profile page, most of your subscription can be displayed, which make each avatar downloaded one after the other. If you can not avoid huge number of downloads, you can spread them between differents domains:

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';

Of course, you'll need a specific vhost definition for NGinx. Why a specific vhost ? Simply because I always disable access logs for statics resources, therefore limiting disk I/O from NGinx:

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;
}

Finally, don't forget to separate static files from PHP scripts so that PHP script will never be stored in static root. If you forgot that, or if you prefer storing all files in the same directory, you may see your config.php leaked.

And, no it's not a bug. Simply, NGinx can not deal natively with PHP files, it will send them as plain text if you don't confiugre it another way.

Disable SMS support

StatusNet has been first designed to clone Twitter. Therefore, devs included SMs support. Since it's not very usefull for personnal instance, you can disable it:

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

Litlle tips I was very happy to discover: subscriptions are now much more easier because you won't need anymore to uncheck SMS, one subscription after the other.

How to find all these configuration options

As usual, all you need is code :)

More seriously, there are 2 files you need to know. First one initializes default config values which will be used if not declared anywhere:

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',
[...]

The second one is a script which will give you all live config values:

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
[...]

This command is very usefull when hacking StatusNet, to understand what's going on. It's also usefull if you want to report an issue in StatusNet forums.

Conclusion

Here is the config.php file you should get if you applied all recipes I detailled here:

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');

Here we're done with that part. Next time we'll see how to connect your StatusNet instance with Twitter and have all messges displayed in real-time.

Enjoy :)

Sources and references

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

About Jean Baptiste FAVRE

I spend most of my free time on the Internet working on GNU/Linux with Debian or CentOS, virtualization with Xen and KVM technology, as well as cluster stacks with corosync and OpenAIS. Particularly interested in Linux, Netfilter, virtualization, monitoring and clusters, most of my personal works are published on this website and others should not delay. By way professional, I manage servers running RedHat or CentOS and VMware ESXi farm.
From time to time, I manage to drop my keyboard and read a book while listening to music, but it never lasts long.

License

Creative Commons License Published under Creative Common by-nc-sa license

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

Table des matières

  1. Previously about StatusNet
  2. Introduction
  3. Disable default plugins
  4. Use asynchronous queue daemons
  5. Use PHP opcode caching extension
  6. Use data cache feature
  7. Checkschema killed me
  8. Tweaking user web interface
  9. Disable SMS support
  10. How to find all these configuration options
  11. Conclusion
  12. Sources and references
  13. About ...
  14. License