Tutorial: Koken beschleunigen

Ich hatte beim dem Portfolio-CMS Koken das Problem, dass die Fotos viel zu lange zum Laden benötigt haben und eine Reihe von Maßnahmen zur Verbesserung der Performance identifiziert, die ich hier teilen möchte.

Für wen sind diese Tipps?

Von diesen Maßnahmen profitieren vor allem Benutzer mit einer VPS bzw. eine dedizierten System, welche Einfluss auf die Einfluss auf ihre Server-Konfiguration nehmen können und ihr System so konfigurieren möchten, dass es mehr Ressourcen für Koken bereitstellt.

Der richtige Bild-Prozessor

Wie die Macher von Koken selber in ihrem Help Center schreiben, ist die Standard-Bibliothek ImageMagick für das Skalieren der Bilder wenn möglich durch den überlegenen Nachfolger GraphicsMagick auszutauschen.

Unter Debian geht das etwa so:

aptitude purge imagemagick; 
aptitude install graphicsmagick;

Man kann zusätzlich die Performance-Drossel durch Einfügen dieser Zeilen an das Ende der storage/configuration/user_setup.php entfernen:

define('DARKROOM_MAGICK_THREADS', false);
define('DARKROOM_MAGICK_MEMORY', false);
define('DARKROOM_MAGICK_MAP', false);

PHP-Prozessen mehr Speicher geben

Die Entwickler empfehlen mindestens 64 MB Speicher für den PHP Prozess. Mir steht auf meiner VPS ein Gigabyte Speicher zur Verfügung, also habe ich in der /etc/php5/apache2/php.ini diese Zeile folgendermaßen geändert:

; Maximum amount of memory a script may consume (128MB)
; http://php.net/memory-limit 
memory_limit = 512M

Pre-Caching der Fotos

Koken zeigt jedem Betrachter die Fotos in der für seinen Bildschirm passendsten Auflösung an. So ist dies auf FULL-HD-Bildschirmen etwa die 1600px-breite Variante und auf mobilen Geräten eher eine kleinere Auflösung.

Hierbei gibt es ein Problem: Das Foto wird erst bei der initialen Abfrage nach der entsprechenden Auflösung entsprechend aus dem größeren Original skaliert und gecached. Dies führt dazu, dass der erste Betrachter des Fotos unter Umständen mehrere Sekunden warten muss, bis sich das Bild bei ihm angezeigt wird. Für den zweiten Besucher ist die Anzeige dann erheblich schneller, da das Foto jetzt in einer kleinen Auflösung vorliegt. Nur das könnte den ersten Besucher bereits vergrault haben.

Leider weigern sich die Koken-Entwickler vehement dagegen eine Pre-Caching-Option für VPS-Besitzer einzubauen, weswegen ein findiger Benutzer sich selber etwas dafür geschrieben hat: https://jerry.im/essays/2014/06/pseudo-solution-to-generate-cache-images-for-koken/

Das Skript besteht aus zwei Teilen: Eine serverseitige cache.url.php extrahiert aus der Datenbank alle Bilder-Links und eine client.php ruft der Reihe nach alle Bilder auf, um die Generierung der Bilder im Cache zu erzwingen.

Der Inhalt der cache.url.php:

<?php

$retina = true;
$key = 'kokenwtf'; //ENTER YOUR OWN KEY

if ($_GET['key'] != $key) die;

$ds = DIRECTORY_SEPARATOR;
$root = dirname(__FILE__);
$content = $root . $ds . 'storage';
$base = '/storage/cache/images';

require($content . $ds . 'configuration' . $ds . 'database.php');

$interface = $KOKEN_DATABASE['driver'];

$query = "SELECT id, filename, file_modified_on FROM {$KOKEN_DATABASE['prefix']}content";

if ($interface == 'mysqli')  
{
    $db_link = mysqli_connect($KOKEN_DATABASE['hostname'], $KOKEN_DATABASE['username'], $KOKEN_DATABASE['password'], null, (int) $KOKEN_DATABASE['port'], $KOKEN_DATABASE['socket']);
    $db_link->select_db($KOKEN_DATABASE['database']);

    if ($query)
    {
        $result = $db_link->query($query);
        if ($result)
        {
            while ($row = $result->fetch_assoc()) {
            $results[] = $row;
            }
            $result->close();
        }
    }
    $db_link->close();
}
else  
{
    mysql_connect($KOKEN_DATABASE['hostname'], $KOKEN_DATABASE['username'], $KOKEN_DATABASE['password']);
    mysql_select_db($KOKEN_DATABASE['database']);

    if ($query)
    {
        $result = mysql_query($query);
        if ($result)
        {
            while ($row = mysql_fetch_assoc($result)) {
            $results[] = $row;
            }
        }
    }
    mysql_close();
}

$requests = array();

foreach($results as $image) {  
    $full_id = str_pad($image['id'], 6, 0, STR_PAD_LEFT);
    $parts = str_split($full_id, $split_length = 3);

    // $url = $base.'/'.$parts[0].'/'.$parts[1];
    $url = '/i.php?/'.$parts[0].'/'.$parts[1];

    list($name, $ext) = explode('.', $image['filename']);

    $size = array(
          'tiny',
          'small',
          'medium',
          'medium_large',
          'large',
          'xlarge',
          'huge'
          );

    foreach($size as $s) {
    $requests[] = $url.'/'.$name.','.$s.'.'.$image['file_modified_on'].'.'.$ext;
    if ($retina) {
        $requests[] = $url.'/'.$name.','.$s.'.2x.'.$image['file_modified_on'].'.'.$ext;
    }
    }
}

echo json_encode($requests);  

Quelle: https://gist.github.com/zllovesuki/b22ed4427c02a5ad033e

Der Inhalt der client.php:

<?php

$website = 'http://yourwebsite.com'; //no trailing slash

$key = 'kokenwtf';

/*
 *
 * RUNNING ON A MACHINE THAT HAS UNLIMITED EXECUTION TIME IS HIGHLY ENCOURAGED
 *
 * If you have firewall installed on your webserver, whitelist your own IP or disable it
 * In case your firewall blacklists you.
 */

$urls = file_get_contents($website.'/cache.url.php?key='.$key);

$requests = json_decode($urls, true);

if ($requests === NULL) die;

foreach($requests as $i) {  
    $ch = curl_init($website.$i);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_exec($ch);
    curl_close($ch);
}

Quelle: https://gist.github.com/zllovesuki/3f5d001868904eb5cb4c

Die cache.url.php wird dann einfach in das Verzeichnis neben die i.php gelegt. Die client.php kann grundsätzlich von überall ausgeführt werden. Um Traffik zu sparen, erledigt dies der Server aber selber.

Ich habe die client.php in ein sprechenderes load_koken_caches.php umbenannt und folgenden Cronjob („crontab -e“) eingerichtet:

* */1 * * * php5 ~/load_koken_caches.php

Wenn ich also neue Bilder hochlade, werden diese spätestens zur vollen Stunde für alle Formate gecached. Somit muss kein Besucher darauf warten.

Troubleshooting

Bei mir funktionierte es leider nicht auf Anhieb so. Ich musste in der cache.url.php die Zeile

$url = $base.'/'.$parts[0].'/'.$parts[1];

durch diese ersetzen:

$url = '/i.php?/'.$parts[0].'/'.$parts[1]

Nach einem Update das Theme neu installieren

Ich habe den Support-Foren entnommen, dass nach Durchführung eines Koken-Updates das Laden von Fotos langsamer erfolgt. Um dies zu korrigieren, hilft eine Neuinstallation des Themes.

Dabei bleiben die am Theme vorgenommenen Einstellungen erhalten.

Trotzdem ist generell ein Backup vor einem Update von Koken oder Themes empfehlenswert.