Wikini

OptimisationWikiniEtudeCache

PagePrincipale :: DerniersChangements :: DerniersCommentaires :: ParametresUtilisateur :: Vous êtes ec2-54-243-17-113.compute-1.amazonaws.com

Etude d'un système de cache des pages




Ressources pour l'optimisation












Une brève discussion avait été proposée quant à la mise en cache des pages dans les OptimisationWikini. Cette page est destinée à en discuter de manière plus détaillée, et surtout à en évaluer les performances. NB.: cette page ne concerne en rien la mise en cache des requêtes SQL.



Pour ma part, je regarde un peu tous azimut (comme je suis débutant, je me forme un peu au passage). J'ai notamment regardé les solutions de cache php. Ce message donne une explication intéressante sur les performances du php. Celui là, un peu copurt, explique le principe des caches.
Encore un comparatif des solutions de cache. Il y en a qui sont relativement faciles à mettre en place.
Une douzaine de classes de cache sont disponible sur phpclasses.org.
J'en teste (ou projette d'en tester) actuellement quelques-uns.

Cas d'utilisation du cache dans le cas d'une page non-interactive (ie : ne possédant d'appel { }).
-- CharlesNepote

Je vais envisager un système de cache valable pour les pages interactives. A part les actions il y a aussi les inclusions raw qui pourraient être prises en compte, je pense que je ne les mettrais pas en cache, de façon à laisser le FormatterRaw? le faire éventuellement (dans une prochaine version de WikiNi bien sûr). -- LordFarquaad

Il y a dans la classe wakka une fonction GetMicroTime?() pour mesurer le temps d'execution d'un bout de code.
-- DavidDelon



Le système de cache que je propose


J'ai impléménté (en fait c'est surtout recopier le FormatterWakka? et le remagnier un peu) un formatter cache.php qui permet de formatter une page normalement sauf qu'il retourne le code php à sauver dans un fichier afin d'utiliser ce dernier directement plutôt que de formatter la page à chaque nouvel affichage. Le gain se fait uniquement côté php car la requête de lecture de la page dans la base de données se fait de façon automatisée bien avant d'avoir sélectionné le handler, or dans le cas présent, seul le HandlerShow est concerné. (cf ClasseWikiMethodeRun?)

Fonctionnement


Todo

Changelog


Sources:

formatters/cache.php (je ne sais pas si je peux le passer sous GPL...)
<?php
/**
 * Fichier cache.php: formatter permettant de construire un fichier php
 * (basé sur le formatter wakka.php)
 * @version $Idv 0.0.1 2005-01-08 $
 * @copyright 2002, Hendrik Mans <hendrik@mans.de>
 * @copyright 2002, 2003 David DELON
 * @copyright 2002, 2003 Charles NEPOTE
 * @copyright 2002, 2003 Patrick PAUL
 * @copyright 2003  Eric DELORD
 * @copyright 2003  Eric FELDSTEIN
 * @copyright 2004  Jean Christophe ANDRÉ
 * @copyright 2005  Didier LOISEAU
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// This may look a bit strange, but all possible formatting tags have to be in a single regular expression for this to work correctly. Yup!
if (!function_exists("CacheCallback"))
{
    function 
CacheCallback($things)
    {
        
$thing $things[1];
        
$result '';

        static 
$oldIndentLevel 0;
        static 
$oldIndentLength 0;
        static 
$indentClosers = array();
        static 
$newIndentSpace = array();
        static 
$br 1;

        global 
$wiki;

        switch (
$thing)
        { 
            
// convert HTML thingies
            
case '<':
                return 
'&lt;';
            case 
'>':
                return 
'&gt;'
            
// styles
            // bold
            
case '**':
                static 
$bold 0;
                return (++
$bold '<strong>' '</strong>'); 
            
// italic
            
case '//':
                static 
$italic 0;
                return (++
$italic '<i>' '</i>'); 
            
// underlinue
            
case '__':
                static 
$underline 0;
                return (++
$underline '<u>' '</u>'); 
            
// monospace
            
case '##':
                static 
$monospace 0;
                return (++
$monospace '<tt>' '</tt>'); 
            
// Deleted
            
case '@@':
                static 
$deleted 0;
                return (++
$deleted '<span class="del">' '</span>'); 
            
// Inserted
            
case '££':
                static 
$inserted 0;
                return (++
$inserted '<span class="add">' '</span>'); 
            
// header level 5
            
case '==':
                static 
$l5 0;
                
$br 0;
                return (++
$l5 "<h5>" "</h5>\n"); 
            
// header level 4
            
case '===':
                static 
$l4 0;
                
$br 0;
                return (++
$l4 "<h4>" "</h4>\n"); 
            
// header level 3
            
case '====':
                static 
$l3 0;
                
$br 0;
                return (++
$l3 "<h3>" "</h3>\n"); 
            
// header level 2
            
case '=====':
                static 
$l2 0;
                
$br 0;
                return (++
$l2 "<h2>" "</h2>\n"); 
            
// header level 1
            
case '======':
                static 
$l1 0;
                
$br 0;
                return (++
$l1 "<h1>" "</h1>\n"); 
            
// forced line breaks
            
case '---':
                return 
"<br />\n";
            default: 
                
// urls
                
if (preg_match("/^([a-z]+:\/\/\S+?)([^[:alnum:]^\/])?$/"$thing$matches))
                { 
                    
// Retrieve url and transform it into valid HTML (htmlentities)
                    
$url htmlentities ($matches[1]);
                    if (!isset(
$matches[2])) $matches[2] = '';
                    return 
"<a href=\"$url\">$url</a>" $matches[2];
                } 
                
// escaped text
                
elseif (preg_match("/^\"\"(.*)\"\"$/s"$thing$matches))
                {
                    return 
str_replace(array('<?''?>''<%''%>'), array('&lt;?''?&gt;''&lt;%''%&gt;'), $matches[1]);
                } 
                
// code text
                
elseif (preg_match("/^\%\%(.*)\%\%$/s"$thing$matches))
                { 
                    
// check if a language has been specified
                    
$code $matches[1];
                    if (
preg_match("/^\((.*)\)(.*)$/s"$code$matches))
                    {
                        list(, 
$language$code) = $matches;
                    } 
                    else
                    {
                        
$language '';
                    } 
                    
// Select formatter for syntaxe hightlighting
                    
if (file_exists("formatters/coloration_" $language ".php"))
                    {
                        
$formatter "coloration_" $language;
                    } 
                    else
                    {
                        
$formatter "code";
                    } 

                    
$output "\n";

                    return 
$output;
                } 
                
// raw inclusion from another wiki
                // (regexp documentation : see "forced link" below)
                
elseif (preg_match("/^\[\[\|(\S*)(\s+(.+))?\]\]$/"$thing$matches))
                {
                    list (, 
$url, , $text) = $matches;
                    if (!
$text$text "404";
                    if (
$url)
                    {
                        
$url .= "/wakka.php?wiki=" $text "/raw";
                        return 
'<?php echo $this->Format($this->Format(\'' addslashes($url) . '\', "raw"), "wakka"); ?>';
                    } 
                    else
                    {
                        return 
"";
                    } 
                } 
                
// forced links
                // \S : any character that is not a whitespace character
                // \s : any whitespace character
                
elseif (preg_match("/^\[\[(\S*)(\s+(.+))?\]\]$/"$thing$matches))
                {
                    if (isset(
$matches[2]) && isset($matches[3]))
                    {
                        list (, 
$url, , $text) = $matches;
                    } 
                    else
                    {
                        list (, 
$url) = $matches;
                    } 
                    if (
$url)
                    {
                        if (
$url != ($url = (preg_replace("/@@|££|\[\[/"""$url))))$result "</span>";
                        if (!isset(
$text)) $text $url;
                        
$text preg_replace("/@@|££|\[\[/"""$text);
                        return 
$result '<?php echo $this->Link(\'' addslashes($url) . '\', "", \'' addcslashes($text'\\\'') . '\'); ?>';
                    } 
                    else
                    {
                        return 
"";
                    } 
                } 
                
// indented text
                
elseif ((preg_match("/\n(\t+|([ ]{1})+)(-|([[:alnum:]]+)\))?/s"$thing$matches)) || (preg_match("/^(\t+|([ ]{1})+)(-|([[:alnum:]]+)\))?/s"$thing$matches) && $brf 1))
                { 
                    
// new line
                    
if (isset($brf)) $br 0;
                    
$result .= ($br "<br />\n" ""); 
                    
// we definitely want no line break in this one.
                    
$br 0
                    
// find out which indent type we want
                    
if (isset($matches[3]))
                    {
                        
$newIndentType $matches[3];
                    } 
                    else
                    {
                        
$newIndentType '';
                    } 
                    if (!
$newIndentType)
                    {
                        
$opener "";
                        
$br 1;
                    } 
                    elseif (
$newIndentType == "-")
                    {
                        
$opener "<ul>\n";
                        
$closer "</li>\n</ul>\n";
                        
$li 1;
                    } 
                    else
                    {
                        
$opener "<ol type=\"" $matches[4] . "\">\n";
                        
$closer "</li>\n</ol>\n";
                        
$li 1;
                    } 
                    
// get new indent level
                    
if (strpos($matches[1], "\t")) $newIndentLevel strlen($matches[1]);
                    else
                    {
                        
$newIndentLevel $oldIndentLevel;
                        
$newIndentLength strlen($matches[1]);
                        if (
$newIndentLength $oldIndentLength)
                        {
                            
$newIndentLevel++;
                            
$newIndentSpace[$newIndentLength] = $newIndentLevel;
                        } 
                        elseif (
$newIndentLength $oldIndentLength)
                            
$newIndentLevel $newIndentSpace[$newIndentLength];
                    } 
                    
$op 0;
                    if (
$newIndentLevel $oldIndentLevel)
                    {
                        for (
$i 0$i $newIndentLevel $oldIndentLevel$i++)
                        {
                            
$result .= $opener;
                            
$op 1;
                            
array_push($indentClosers$closer);
                        } 
                    } 
                    elseif (
$newIndentLevel $oldIndentLevel)
                    {
                        for (
$i 0$i $oldIndentLevel $newIndentLevel$i++)
                        {
                            
$op 1;
                            
$result .= array_pop($indentClosers);
                            if (isset(
$oldIndentLevel) && isset($li)) $result .= "</li>\n";
                        } 
                    } 

                    if (isset(
$li) && $op$result .= "<li>";
                    elseif (isset(
$li))
                        
$result .= "</li>\n<li>";

                    
$oldIndentLevel $newIndentLevel;
                    
$oldIndentLength $newIndentLength;

                    return 
$result;
                } 
                
// new lines
                
elseif ($thing == "\n")
                { 
                    
// if we got here, there was no tab in the next line; this means that we can close all open indents.
                    
$c count($indentClosers);
                    for (
$i 0$i $c$i++)
                    {
                        
$result .= array_pop($indentClosers);
                        
$br 0;
                    } 
                    
$oldIndentLevel 0;
                    
$oldIndentLength 0;
                    
$newIndentSpace = array();

                    
$result .= ($br "<br />\n" "\n");
                    
$br 1;
                    return 
$result;
                } 
                
// events
                
elseif (preg_match("/^\{\{(.*?)\}\}$/s"$thing$matches))
                {
                    if (
$matches[1])
                        return 
'<?php echo $this->Action(\'' addcslashes($matches[1], '\\\'') . '\'); ?>';
                    else
                        return 
"{{}}";
                } 
                
// interwiki links!
                
elseif (preg_match("/^" WN_UPPER WN_CHAR "+[:](" WN_CHAR2 "*)$/s"$thing))
                {
                    return 
'<?php echo $this->Link(\'' addslashes($thing) . '\'); ?>';
                } 
                
// wiki links!
                
elseif (preg_match("/^" WN_UPPER WN_LOWER "+" WN_UPPER_NUM WN_CHAR "*$/s"$thing))
                {
                    return 
'<?php echo $this->Link(\'' addslashes($thing) . '\'); ?>';
                } 
                
// separators
                
else if (preg_match("/-{4,}/"$thing$matches))
                { 
                    
// TODO: This could probably be improved for situations where someone puts text on the same line as a separator.
                    // Which is a stupid thing to do anyway! HAW HAW! Ahem.
                    
$br 0;
                    return 
"<hr />\n";
                } 
                
// if we reach this point, it must have been an accident.
                
return $thing;
        } 
    } 
}
 
$text str_replace("\r"""$text);
$text chop($text) . "\n";
$text preg_replace_callback("/(\%\%.*?\%\%|" "\"\".*?\"\"|" "\[\[.*?\]\]|" "\b[[:lower:]]+:\/\/\S+|" "\*\*|\#\#|@@|££|__|<|>|\/\/|" "======|=====|====|===|==|" "-{4,}|---|" "\n(\t+|([ ]{1})+)(-|[[:alnum:]]+\))?|" "^(\t+|([ ]{1})+)(-|[[:alnum:]]+\))?|" "\{\{.*?\}\}|" "\b" WN_UPPER WN_CHAR "+[:](" WN_CHAR2 "*)\b|" "\b(" WN_UPPER WN_LOWER "+" WN_UPPER_NUM WN_CHAR "*)\b|" "\n)/ms""CacheCallback"$text); 
// we're cutting the last <br />
$text preg_replace('/<br \/>$/'''trim($text));
echo 
"<?php\nif (!defined('WIKINI_VERSION'))\n\texit('Acc&egrave;s direct interdit');\n?>$text";

?>

Modification à apporter au HandlerShow: remplacer la partie "display page" par:
<?php
        
// display page
        // si la cache est activée, on l'utilise:
        
if ($cachedir $this->GetConfigValue('pagecache_dir'))
        {
            
// si IncludeBuffered ne retourne pas false, il a su lire la page en cache
            
if (false !== $page $this->IncludeBuffered($file $this->page['id'] . '.php'falsenull$cachedir))
            {
                echo 
$page;
            } 
            
// sinon cela signifie que la page n'était pas encore en cache, il faut donc l'y mettre
            
else
            {
                
$fp fopen($cachedir $file'w+');
                
fputs($fp$this->Format($this->page['body'], 'cache'));
                
fclose($fp);
                
// inutile de reformatter la page maintenant qu'on a une version disponible en cache...
                
include $cachedir $file;
            } 
        } 
        
// la cache est désactivée
        
else echo $this->Format($this->page["body"], "wakka"); 
?>
Et voilà, n'oubliez pas d'ajouter la variable de configuration "pagecache_dir" et d'autoriser le répertoire en question en écriture et le tour est joué ;-) Il ne reste qu'à évaluer le gain de performances...

Exemple de version en cache d'une page: l'ActionBackLinks:
<?php
if (!defined('WIKINI_VERSION'))
    exit(
'Accès direct interdit');
?>Action permettant d'insérer la liste de toutes les pages faisant référence à la page courante. Dans cette présente page, {{backlinks}} donne ceci :<br />
<br />
<?php echo $this->Action('BackLinks'); ?><br />
<br />
Sur la page personnelle d'un utilisateur, cette action affichera aussi les pages dont il est le propriétaire ou le dernier modificateur.<br />
<br />
<h2> Paramètres </h2>

<br />
<h3> Paramètre "page" </h3>

Le paramètre "page" (<?php echo $this->Link('http://www.wikini.net'""'WikiNi'); ?> &gt;= 0.4.1) permet de spécifier une page différente de la page courante.<br />
Par exemple <tt>{{backlinks page="PagePrincipale"}}</tt><br />
Ce paramètre peut être utile, par exemple :<br />
<ul>
<li> pour améliorer le <?php echo $this->Link('TableauDeBordDeCeWiki'); ?> : il suffit d'ajouter la liste des pages liées à la page AFaire pour connaître les pages qui doivent faire l'objet d'un travail</li>
<li> pour consolider des données sur une seule page : par exemple la liste des pages liées aux pages EstUnHomme et EstUneFemme</li>
<li> autres ?</li>
</ul>

<br />
<h3> Paramètre "exclude" </h3>

Le paramètre "exclude" (<?php echo $this->Link('http://www.wikini.net'""'WikiNi'); ?> &gt;= 0.4.1) permet de spécifier des pages à exclure de la liste des pages qui ont pourtant un lien vers la page de référence.<br />
Il est en effet parfois génant d'afficher la totalité des pages faisant référence à une page. Par exemple, la page AFaire liste la page CharlesNepoteVeilleSurInternet alors que, non seulement cette information n'a pas d'intérêt mais elle pollue en outre la lecture de cette page. Autre exemple, une page MamiFeres a intérêt par exemple à lister CheVal et ElePhant mais pas nécessairement AniMaux qui est une catégorie supérieure. Je suggère donc la création d'un paramètre exclude destiné à exclure certaines pages : par exemple :<br />
<tt>{{backlinks exclude="AniMaux"}}</tt><br />
Le paramètre "exclude" peut contenir plusieurs noms de page séparés par des ";", par exemple : <tt>{{backlinks exclude="AniMaux; PagePrincipale"}}</tt><br />
<br />
<hr />

<?php echo $this->Action('trail toc="ListeDesActionsWikini"'); ?>

-- LordFarquaad [LordFarquaadASuivre]

serait-il possible d'avoir un cache XHTML strictement statique du wiki en lecture ? De telle manière que le wiki soit accessible en lecture même si PHP et MySQL sont complètement plantés. La majorité des hits n'étant que des actions de lecture, ça ferait une sacré économie ?
ça pose le problème des actions dans les pages, il faudrait qu'elles soient appliquées lors de la modification d'une page et non à son affichage... -- ActionsReseauxNumeriques
On basculerait alors en mode dynamique PHP/MySQL seulement lors des modifications ou des actions spécifiques telles que affichage des difs ou recherche dans le wiki...
ça peut sembler une piste valable ? -- ActionsReseauxNumeriques




Gestion de la cache dynamique

J'entends par "cache dynamique" la cache qui est générée (et gérée) dynamiquement par WikiNi (ou autre) lors de la génération d'une page à afficher, afin d'éviter de faire plusieurs fois la même chose.

J'ignorer si cela a un autre nom mais j'espère que c'est assez précis comme définition...

État actuel

Dans l'état actuel des choses, WikiNi fait énormément de requêtes SQL pour générer une page, principalement pour savoir si une page existe lors de la création des liens. Ce procédé étant énormément consomateur de ressources, il essaie de le réduire en évitant de charger plusieurs fois la même page, en mettant en cache les pages chargées.

On pourrait tout d'abord se poser la question de l'intérêt que cela présente:

Évolutions possibles


Il n'y a pas de commentaire sur cette page. [Afficher commentaires/formulaire]