contao / core

Contao 3 → see contao/contao for Contao 4
GNU Lesser General Public License v3.0
490 stars 214 forks source link

C3.0.4: Problem mit Cache Dateien bei gleichzeitigem schreiben und lesen #5307

Closed BugBuster1701 closed 11 years ago

BugBuster1701 commented 11 years ago

Mal angenommen der Cache ist leer. (cron, frische Installation, ...) Nun folgen 2 Zugriffe zu selben Zeit. Der erste bewirkt, dass z.b. die Cache Datei dca/tl_module.php geschrieben wird. Während der geschrieben wird, fängt der zweite schon an die zu lesen, da die Datei ja bereits existiert. Nun ist das lesen aber schneller als das schreiben und die Sache explodiert.

PHP Warning:  Unterminated comment starting line ...
PHP Parse error:  syntax error, unexpected '*' in ....
PHP Parse error:  syntax error, unexpected ''load'=>'eager' ...

Ich habe mal mit 100 Requests, davon max 10 gleichzeitig, gestestet, davon sind 14 auf Fehler gelaufen.

Das ist keine Theorie. Getestet habe ich das, da es in der Praxis bereits aufgetreten ist und ich die Ursache, die als Theorie vorlag, prüfen wollte.

leofeyer commented 11 years ago

Sowohl die Model-Relationen als (in Contao 3.1) auch die .xlf-Daten.

tristanlins commented 11 years ago

Also es geht allgemein um die Caching Mechanismen? Erstmal Grundlage, Caching sollte so laufen:

-> getIrgendwasCachefähiges()
      ? cache existiert
          -> return cache
      : erstelle Daten live
          -> lege Daten in Cache
          -> return Daten

Soweit sind wir uns denke ich einig? Wenn ich mir die System::loadLanguageFile anschaue, dann verhält die sich auch genau so: https://github.com/contao/core/blob/master/system/modules/core/library/Contao/System.php#L371 Der Sprachencache ist also bereits so angelegt, das er nicht notwendig ist. Sind die anderen Caches nicht auch schon so aufgebaut? Immerhin müssten die Caches doch die Option "internen Cache umgehen" berücksichtigen?

Wenn wir hier sind, wollen wir einen Schritt weiter gehen. Um das Caching in einen Cron auszulagern ist eigentlich nicht sonderlich viel notwendig. Eigentlich muss nur der Punkt -> lege Daten in Cache optional gemacht werden. Z.B. über eine Konstante/Parameter/whatever.

In der loadLanguageFile könnte dass dann so aussehen:

...
if (!$GLOBALS['TL_CONFIG']['bypassCache'] && file_exists($strCacheFile))
{
    include $strCacheFallback;
    include $strCacheFile;
}
else if (CACHE_NOGENERATE)
{
    // Parse all active modules
    foreach (\Config::getInstance()->getActiveModules() as $strModule)
    {
        $strFallback = TL_ROOT . '/system/modules/' . $strModule . '/languages/en/' . $strName . '.php';

        if (file_exists($strFallback))
        {
            include $strFallback;
        }

        if ($strLanguage == 'en')
        {
            continue;
        }

        $strFile = TL_ROOT . '/system/modules/' . $strModule . '/languages/' . $strLanguage . '/' . $strName . '.php';

        if (file_exists($strFile))
        {
            include $strFile;
        }
    }
}
else
{
    // Generate the cache files
    $objCacheFallback = new \File('system/cache/language/en/' . $strName . '.php');
    $objCacheFallback->write('<?php '); // add one space to prevent the "unexpected $end" error

    $objCacheFile = new \File('system/cache/language/' . $strLanguage . '/' . $strName . '.php');
    $objCacheFile->write('<?php '); // add one space to prevent the "unexpected $end" error

    // Parse all active modules
    foreach (\Config::getInstance()->getActiveModules() as $strModule)
    {
        $strFallback = TL_ROOT . '/system/modules/' . $strModule . '/languages/en/' . $strName . '.php';

        if (file_exists($strFallback))
        {
            $objCacheFallback->append(static::readPhpFileWithoutTags($strFallback));
            include $strFallback;
        }

        if ($strLanguage == 'en')
        {
            continue;
        }

        $strFile = TL_ROOT . '/system/modules/' . $strModule . '/languages/' . $strLanguage . '/' . $strName . '.php';

        if (file_exists($strFile))
        {
            $objCacheFile->append(static::readPhpFileWithoutTags($strFile));
            include $strFile;
        }
    }

    $objCacheFallback->close();
    $objCacheFile->close();
}
...

Das Cron Script / Wartungsmodul müsste dann nur noch alle Module durch laufen und alle Sprachdateien laden. Das ist zwar eine Ressourcenhungrige Operation, stört aber in einem Cron oder im Wartungsmodul nicht wirklich. Das Cron Script müsste nur ein define('CACHE_NOGENERATE', false) machen, während define('CACHE_NOGENERATE', true) die Standardeinstellung für den Normalbetrieb wäre. Das würde im bypassCache Modus sogar dafür sorgen, dass nicht ständig die Cache Dateien neu geschrieben werden.

leofeyer commented 11 years ago

Soweit sind wir uns denke ich einig?

Soweit sind wir uns einig und nach genau diesem Prinzip funktioniert der Cache in Contao auch. Aus Effizienzgründen werden aber nicht die ganzen Einzeldateien per include aufgerufen, sondern nur die aggregierte temporäre Datei kurz bevor sie an ihre eigentliche Position verschoben wird. Wenn die Option "internen Cache umgehen" aktiviert ist, könnte man die Daten auch in einen String schreiben und ihn durch eval() schicken, um sich das Erstellen der Dateien zu sparen.

Würden wir Leo Unglaub's Idee implementieren, wäre ich jedoch dafür, dass die Routine zur Erstellung der Cache-Dateien komplett in den Automator ausgelagert wird. Die loadLanguageFile() sollte dann nur noch "Lade aus dem Cache" oder "Lade die Einzeldateien" machen.

Die Frage ist, ob das alles notwendig ist bzw. irgendwelche lohnenswerten Vorteil bringt.

leofeyer commented 11 years ago

Ich habe die Änderungen für Contao 3.1 in 5c7ac78013fcb30f72dc4c04cc26bc543d55bb50 vorgenommen.

tristanlins commented 11 years ago

Ja, das sieht sehr gut aus, werde ich im Rahmen meines nächsten Projektes auch mal testen ;-)