Loading INI caches takes time. It is not unusual to see in the time accumulators (in the debug output) that loading these caches takes 15% or more of the total run time on a well cached production site. In this article we will show you how to reduce this time to almost zero by a simple patch of ezini.php.
Below you will find the download links for the patch. Use ezini_3.9.3plus.patch for eZ Publish 3.9.3 and later or 3.10, or ezini_4.0.patch for eZ Publish 4. After applying the patch, remove INI caches (if unsure, remove all caches).
You will find new file settings.php in your eZ Publish root directory with the following content:
<?php
$GLOBALS['eZINIForceUsingCache'] = true;
?>
If the global variable eZINIForceUsingCache is true, eZ Publish will determine the INI cache filename and if the cache file exists, it will use it without checking if it is up-to-date. As there are seldom changes in INI settings on production sites, such checking is not neeed. If you need to alter some settings, just make the changes and clear the INI caches. During the development, you can set eZINIForceUsingCache to false and let eZ Publish always check for changes in an original INI file or its overrides.
How much faster will your site become depends on many factors, so we will rather give a couple of examples:
- The homepage of www.seeds.no is almost 17% quicker (the mean time per request dropped from 102 ms to 85 ms), for blog entries the mean request time dropped by 13%.
- Peter Kudlicka has tested the patch on one of the eZ Systems' biggest projects with around 30 extensions and request time dropped down by almost 30%!
Please let us know your experience with the patch by using comments below or our contact form.
How it works
Even on a well cached site, eZ Publish loads quite a few INI files. There is no need to say that for one set of INI settings represented by a filename, eZ Publish has to traverse through several directories, looking for the original INI file and its overrides. Which directories to look into depends on the current eZ Publish state: the current siteaccesses (if already determined), list of enabled extensions, etc. To avoid parsing the original INI file and its overrides again and again, eZ Publish caches the parsed settings for every state as an PHP array.
The name of a cache file is a hash (MD5) of a string combined from paths of original and override files (and some other information). If your code asks for the instance of, say, site.ini, eZ Publish will have to first find out which directories might include relevant override files (that depends on the current eZ Publish state) and traverse them looking for these files (site.ini.append and site.ini.append.php) to be able to determine the cache filename! As you might guess, this takes a lot of time.
The patch changes the way how the cache filename is built. Instead of using a hash of paths to particular INI files, it uses a hash of serialized array with a couple of variables which are relevant in determining which directories can contain override INI files (see function cacheFileName() in ezini.php). The cache filenames will still be unique for different INI files and different states but can be determined without looking for all override files.
In addition, the patch adds the list of used original and override INI files to cache files as a PHP comment.
INI patch (version for eZ Publish 3)
--- lib/ezutils/classes/ezini.php.orig 2007-08-22 16:02:33.000000000 +0200
+++ lib/ezutils/classes/ezini.php 2008-04-28 09:28:41.000000000 +0200
@@ -273,6 +273,31 @@
}
}
+ /* private */
+ function cacheFileName( $placement = false )
+ {
+ $settings = array(
+ 'RootDir' => $this->RootDir,
+ 'FileName' => $this->FileName,
+ 'DirectAccess' => $this->DirectAccess,
+ 'Placement' => $placement,
+ );
+ if ( !$this->DirectAccess )
+ {
+ $settings['OverrideDirs'] = ( $this->UseLocalOverrides )? $this->LocalOverrideDirArray:
+ isset( $GLOBALS['eZINIOverrideDirList'] )? $GLOBALS['eZINIOverrideDirList']:
+ null;
+ }
+ if ( $this->UseTextCodec )
+ {
+ include_once( "lib/ezi18n/classes/eztextcodec.php" );
+ $settings['Charset'] = eZTextCodec::internalCharset();
+ }
+
+ $cacheFileName = md5( serialize( $settings ) ) . '.php';
+ return $cacheFileName;
+ }
+
/*!
\private
Looks trough all known settings and override folders to find relevant INI files.
@@ -343,6 +368,21 @@
}
}
+ /* private */
+ function forceUsingCache()
+ {
+ if ( !isset( $GLOBALS['eZINIForceUsingCache'] ) )
+ {
+ @include_once( 'settings.php' );
+ if ( !isset( $GLOBALS['eZINIForceUsingCache'] ) )
+ {
+ $GLOBALS['eZINIForceUsingCache'] = false;
+ }
+ }
+
+ return $GLOBALS['eZINIForceUsingCache'];
+ }
+
/*!
\private
Will load a cached version of the ini file if it exists,
@@ -354,36 +394,13 @@
if ( $reset )
$this->reset();
$cachedDir = "var/cache/ini/";
-
- eZDebug::accumulatorStart( 'ini_find_files', 'ini_load', 'FindInputFiles' );
- $this->findInputFiles( $inputFiles, $iniFile );
- eZDebug::accumulatorStop( 'ini_find_files' );
- if ( count( $inputFiles ) == 0 )
- {
- eZDebug::accumulatorStop( 'ini' );
- return false;
- }
+ $fileName = $this->cacheFileName( $placement );
/* if ( strstr( end( $inputFiles ), 'settings/override/' ) )
{
$overrideINIFile = array_pop( $inputFiles );
}*/
- $md5_input = '';
- foreach ( $inputFiles as $inputFile )
- {
- $md5_input .= $inputFile. "\n";
- }
- if ( $this->UseTextCodec )
- {
- include_once( "lib/ezi18n/classes/eztextcodec.php" );
- $md5_input .= '-' . eZTextCodec::internalCharset();
- }
- if ( $placement )
- {
- $md5_input .= '-placement';
- }
- $fileName = md5( $md5_input ) . ".php";
$cachedFile = $cachedDir . $fileName;
if ( $placement )
{
@@ -394,28 +411,45 @@
$this->CacheFile = $cachedFile;
}
- $inputTime = false;
- // check for modifications
- foreach ( $inputFiles as $inputFile )
+ $forceUsingCache = $this->forceUsingCache();
+ if ( $forceUsingCache && file_exists( $cachedFile ) )
{
- $fileTime = filemtime( $inputFile );
- if ( $inputTime === false or
- $fileTime > $inputTime )
- $inputTime = $fileTime;
- }
-
- $loadCache = false;
- $cacheTime = false;
- if ( file_exists( $cachedFile ) )
- {
- $fileInfo = @stat( $cachedFile );
- if ( $fileInfo )
- {
- $cacheTime = $fileInfo['mtime'];
- $loadCache = true;
- if ( $cacheTime < $inputTime )
+ $loadCache = true;
+ }
+ else
+ {
+ eZDebug::accumulatorStart( 'ini_find_files', 'ini_load', 'FindInputFiles' );
+ $this->findInputFiles( $inputFiles, $iniFile );
+ eZDebug::accumulatorStop( 'ini_find_files' );
+ if ( count( $inputFiles ) == 0 )
+ {
+ eZDebug::accumulatorStop( 'ini' );
+ return false;
+ }
+
+ $inputTime = false;
+ // check for modifications
+ foreach ( $inputFiles as $inputFile )
+ {
+ $fileTime = filemtime( $inputFile );
+ if ( $inputTime === false or
+ $fileTime > $inputTime )
+ $inputTime = $fileTime;
+ }
+
+ $loadCache = false;
+ $cacheTime = false;
+ if ( file_exists( $cachedFile ) )
+ {
+ $fileInfo = @stat( $cachedFile );
+ if ( $fileInfo )
{
- $loadCache = false;
+ $cacheTime = $fileInfo['mtime'];
+ $loadCache = true;
+ if ( $cacheTime < $inputTime )
+ {
+ $loadCache = false;
+ }
}
}
}
@@ -459,7 +493,7 @@
$this->parse( $inputFiles, $iniFile, false, $placement );
eZDebug::accumulatorStop( 'ini_files_1' );
eZDebug::accumulatorStart( 'ini_files_2', 'ini_load', 'Save Cache' );
- $cacheSaved = $this->saveCache( $cachedDir, $cachedFile, $placement ? $this->BlockValuesPlacement : $this->BlockValues );
+ $cacheSaved = $this->saveCache( $cachedDir, $cachedFile, $placement ? $this->BlockValuesPlacement : $this->BlockValues, $inputFiles );
eZDebug::accumulatorStop( 'ini_files_2' );
if ( $cacheSaved )
@@ -477,7 +511,7 @@
\private
Stores the content of the INI object to the cache file \a $cachedFile.
*/
- function saveCache( $cachedDir, $cachedFile, $data )
+ function saveCache( $cachedDir, $cachedFile, $data, $inputFiles )
{
if ( !file_exists( $cachedDir ) )
{
@@ -496,7 +530,9 @@
eZDebug::writeError( "Couldn't create cache file '$cachedFile', perhaps wrong permissions", "eZINI" );
return false;
}
- fwrite( $fp, "<?php\n\$eZIniCacheCodeDate = " . EZ_INI_CACHE_CODE_DATE . ";\n" );
+ fwrite( $fp, "<?php\n/*\n " );
+ fwrite( $fp, implode( "\n ", $inputFiles ) );
+ fwrite( $fp, "\n*/\n\$eZIniCacheCodeDate = " . EZ_INI_CACHE_CODE_DATE . ";\n" );
if ( $this->Codec )
fwrite( $fp, "\$charset = \"".$this->Codec->RequestedOutputCharsetCode."\";\n" );
--- settings.php.orig 2008-04-28 09:39:44.000000000 +0200
+++ settings.php 2008-04-28 09:42:09.000000000 +0200
@@ -0,0 +1,5 @@
+<?php
+
+$GLOBALS['eZINIForceUsingCache'] = true;
+
+?>