1+ for smarter cache

On 12/2/05, [EMAIL PROTECTED] <[EMAIL PROTECTED]> wrote:
>
> > > So, are you interested in PHP realization with main idea explained?
> > Yes
>
> Ok, hope it will be interesting:
>
> In my CMS there is (there was?) such an abstraction: node. Node - it's
> everything - it's a news, article or any other content unit.
>
> Node has all CRUD operations through the functions: get, add, edit,
> delete.
>
> There is also such a module - cache: it track two things - dependence
> and time. Dependence, in fact, that's actually, one file, which is
> `touch`ed automaticly every time, any non-read node operations is
> perfomed (it is just a cache_update_dependence() call from node
> functions).
>
> Then, any page, which may want to use cache, has the following lines
> (it can be put in one place/class, in a normal designed,
> object-oriented, enviroment):
> ============
> //Predefine cache data
> $CacheID = basename($_SERVER['SCRIPT_NAME']);
> $CurrTs = time(); $CacheExpireTs =  mktime(0, 0, 1, date('m', $CurrTs),
> (date('d', $CurrTs) + 1), date('Y', $CurrTs)); //first second of the
> next day
> $CacheDependencies = array('taxonomy_data', 'taxonomy_function',
> 'taxonomy_hierarchy', 'taxonomy_nodes', 'source_data', 'source_nodes',
> 'nodes');} //show nid
>
> #Check cache
> if ($CachedContent = cache_get($CacheID, $ScriptArgs,
> $CacheDependencies)) {echo $CachedContent;} //Return cached page
> content
> else { //Get and construct page content
>    //content generation here
> }//if($CachedContent
> ============
>
> Dependencies - it's just an empty files, with the same names, as an
> existen tables.
>
> What cache do behind the scene:
> - it check, cache validness: if table was modified (and touched by the
> node functions) recently or if cache should be deleted by time-out - it
> is droped and Null is returned; otherwise - cache content, which is
> readen from the file, which name is an md5 sum from the script
> arguments (special function for that).
> - it check cache existens;
> - it also empty cache, by cron, by calling cache_clean() function,
> which delete all cache object, starting from the oldest one, untill
> predefined (in settings) size will be reached (so we will always have
> the most recent XXX Mbytes of cache).
>
> cache.inc source code below.
> Please, do not blame me too much - yes, this is not an object oriented
> code, and it is not perfect, by I just want, that the idea of such a
> cache will not dyu with the code (when it was the time, when I need a
> cache, I do not find any cache sollutions based on the DB changes
> track, so I have to invent it on my own).
>
> cache.inc (comments were written once and was not modified with the
> code updates)
> ==================================================================
> <?php
> /* Work algorithm: track cache validness using expiration time & (db)
> dependencies
> Rules:
>         - cache is decided to be dirty if cache timestamp is invalid _or_ if
> one of the dependencies is out of the date
>         - correct dependencies is a caller problem and should be specified at
> the cache_get function
>         - database tables (dependencies) update is core functions problem
>         - the only thing cache take on itself - it's section handle (including
> table (dep) completion: 'taxonomy_data' dep will be completed to the
> 'SECTION._taxonomy_data' )
>
> Some details: expiration is computed using file modification time
> (cache expire timestamp == file modification time), so, cache creation
> time is not available.
> Algorithm:
>         - two stuff is tracked: cache expire time & dependence update time
>         - cache has three dates: creation, last acces and expire, that is
> inode change time of file, last file access time and file modification
> accordingly
>         - dependencies tracked in a following algorithm: if dep. access time
> is more than cache creation, than cache is obsolete, and it's dropped
> */
> //ToDo:
> // - tune tables updates track
> // - implement function cache
>
> ini_set('ignore_user_abort', 1); //partly loaded pages is useless and
> should never be in cache (suppose, that if script is failed, than it
> will never store cache, cause it's one of the last deal)
>
> //Convert function args to the fs storable hash (md5 used);
> func_get_args - for function and script_args for script should be used;
> valid for no more than a two level array.
> function args_hash($Args = NULL) {
>         if (!is_array($Args) && !is_string($Args)) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         if (is_string($Args)) {$ArgsString = $Args;} //just a line
>         else if (is_array($Args)) {
>                 $ArgsString = '';
>                 while(list($Key, $Val) = each($Args)) { //no more than a two 
> level
> array reconstructor
>                         if (!is_array($Val)) {$ArgsString .= $Key.$Val;}
>                         else if (is_array($Val)) {while(list($SubKey, 
> $SubVal) = each($Val))
> {$ArgsString .= $SubKey.$SubVal;}}
>                 }//while
>         } else {
>                 func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;
>         }//if(is_string
>
> return md5($ArgsString);
> }//function
>
>
> //Just check enviroment to the errors; return FALSE & log event, that
> may "polute" cache
> function cache_env_dirty() {
>
>         if ($ErrorMessage = error_description_read()) {
>                 debug_log('Cache', __FUNCTION__.": cache interrupted because 
> of the
> unhandled error.");
>                 $GLOBALS['CashSwitch'] = 0;
>                 return $ErrorMessage;
>         } else {
>                 return FALSE;
>         } //if($ErrorMessage
>
> }//function
>
>
> //Should be called by any function, that insert/update any info to the
> database; section append is optional, cause other db function know full
> table name
> function cache_update_dependence($Dependence = NULL, $SectionAppend =
> 0) {
>         global $CacheStatDir;
>
>         if ($SectionAppend) {if (!defined('SECTION')) {$Section =
> 'UnSectioned';} else {$Section = strtolower(SECTION);}}
>
>         if (!is_string($Dependence) || (($SectionAppend != 0) &&
> ($SectionAppend != 1))) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         if (!is_dir($CacheStatDir)) { //dependence dir should exist allready
>                 global $ErrMsg;
>                 func_error_handler(array(__FUNCTION__, func_get_args(),
> "$CacheStatDir {$ErrMsg['UnexistenDirectory']}.
> {$ErrMsg['CachePreventCorruptionAndOff']}"));
>                 $GLOBALS['CashSwitch'] = 0;
>                 return FALSE;
>         }//if(!is_dir
>
>         $DependenceFullName =
> ($SectionAppend)?"{$Section}_{$Dependence}":$Dependence;
>         $DependenceFullPath = "$CacheStatDir/$DependenceFullName";
>
>         $CurrTime = time();
>         $Result = touch($DependenceFullPath, $CurrTime, $CurrTime);
>
>         if ($Result) {
>                 debug_log('Cache', __FUNCTION__.": '$Dependence' dependence 
> updated;
> access & modification time is set to ".timestamp2date($CurrTime, 1, '
> ')." ($CurrTime)");
>                 return TRUE;
>         } else {
>                 global $ErrMsg;
>                 func_error_handler(array(__FUNCTION__, func_get_args(), 
> "$Dependence
> {$ErrMsg['DepUpdateFail']}.
> {$ErrMsg['CachePreventCorruptionAndOff']}"));
>                 $GLOBALS['CashSwitch'] = 0;
>                 return FALSE;
>         }//if($Result
>
> }//function
>
>
> //===>> Function store_page_cache <<===
> function cache_store($Item = NULL, $Args = NULL, $Content = NULL,
> $Expire = NULL) {
>         global $CacheFilesRoot, $ErrMsg;
>         settype($Expire, 'int');
>
>         if (!defined('SECTION')) {$Section = 'UnSectioned';} else {$Section =
> strtolower(SECTION);}
>
>         //Arguments check
>         if (!is_string($Item) || (!is_string($Args) && !is_array($Args)) ||
> !is_string($Content) || !is_int($Expire))
> {func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         //Check unhandled errors
>         if ($UnhandledError = cache_env_dirty())
> {func_error_handler(array(__FUNCTION__, func_get_args(),
> "[i]'$UnhandledError'[/i].
> {$ErrMsg['CachePreventCorruptionAndOff']}.")); return FALSE;}
>
>         //Global cache switch check
>         if (!$GLOBALS['CashSwitch']) {return FALSE;}
>
>         $ArgsHash = args_hash($Args);
>         $ItemDir = "$CacheFilesRoot/$Section/$Item";
>         $TargetFile = "$ItemDir/$ArgsHash";
>
>         //Destination dir check
>         if (!is_dir($ItemDir)) {if (!mk_r_dir($ItemDir))
> {func_error_handler(array(__FUNCTION__, func_get_args(), "$ItemDir
> {$ErrMsg['DirCantBeCreated']}")); return FALSE;}}
>
>         if (file_store($TargetFile, $Content) && touch($TargetFile, $Expire))
> {
>                 debug_log('Cache', __FUNCTION__."($Item, $ArgsHash, content 
> (length):
> ".strlen($Content).", expire: ".timestamp2date($Expire, 1, ' ')."
> ($Expire)) - done.");
>                 return TRUE; //content stored, cach marked - all is Ok
>         } else {
>                 func_error_handler(array(__FUNCTION__, func_get_args(), 
> "$TargetFile
> {$ErrMsg['FileCantBeCreatedUpdated']}")); return FALSE;
>         }//if(file_store
>
> }//function
>
>
> //Return cache
> function cache_get($Item = NULL, $Args = NULL, $Dependencies = array())
> {
>         global $CacheFilesRoot, $CacheStatDir;
>
>         $FuncArgs = func_get_args(); debug_log('Cache',
> __FUNCTION__."(".array2string($FuncArgs).")");
>
>         if (!defined('SECTION')) {$Section = 'UnSectioned';} else {$Section =
> strtolower(SECTION);}
>
>         if (!is_string($Item) || (!is_string($Args) && !is_array($Args)) ||
> !is_array($Dependencies)) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         if (!$GLOBALS['CashSwitch']) {return FALSE;} //cache is switched off
>
>         $ArgsHash = args_hash($Args);
>         $DependenciesAmount = count($Dependencies);
>         $Dir = $CacheFilesRoot."/".$Section."/".$Item;
>         $CacheFile = $Dir."/".$ArgsHash;
>
>         if (!is_dir($Dir) || !is_file($CacheFile)) {debug_log('Cache',
> __FUNCTION__."($Item, $ArgsHash, $DependenciesAmount) there is no cache
> file: '$CacheFile'"); return FALSE;}
>
>         debug_log('Cache', __FUNCTION__."('$Item', '$ArgsHash',
> '$DependenciesAmount') - file '$CacheFile'");
>
>         $CheckingCurrTimestamp = time();
>         $CacheExpirationTs = filemtime($CacheFile);
>
>         if ($CacheExpirationTs <= $CheckingCurrTimestamp) {
>                 debug_log('Cache', __FUNCTION__."($Item, $ArgsHash,
> $DependenciesAmount) cache expired at
> ".timestamp2date($CacheExpirationTs, 1, ' '));
>                 cache_drop($Item, $ArgsHash);
>                 return FALSE;
>         } else if ($DependenciesAmount) {
>
>                 if (!is_dir($CacheStatDir)) { //dependence dir should exist 
> allready
>                         func_error_handler(array(__FUNCTION__, 
> func_get_args(),
> "$CacheStatDir {$ErrMsg['UnexistenDirectory']}.
> {$ErrMsg['CachePreventCorruptionAndOff']}"));
>                         $GLOBALS['CashSwitch'] = 0;
>                         return FALSE;
>                 }//if(!is_dir
>
>                 //Return FALSE, if any of the dep is updated
>                 for ($i = 0; $i < $DependenciesAmount; $i++) {
>                         $CurrDep = 
> "$CacheStatDir/{$Section}_{$Dependencies[$i]}";
>                         $CacheCreationTs = filectime($CacheFile);
>                         $CurrDepUpdateTs = fileatime($CurrDep);
>                         if ($CacheCreationTs <= $CurrDepUpdateTs) {
>                                 debug_log('Cache', __FUNCTION__."($Item, 
> $ArgsHash,
> $DependenciesAmount) $CurrDep dependence expired at
> ".timestamp2date($CurrDepUpdateTs, 1, ' '));
>                                 cache_drop($Item, $ArgsHash);
>                                 return FALSE;
>                         }//if(filemtime
>                 }//for
>
>         }//if($DependenciesAmount
>
>         //If all about code is passed, then expire & dependencies is Ok
>         debug_log('Cache', __FUNCTION__."($Item, $ArgsHash,
> $DependenciesAmount) returning cache content");
> return file_read($CacheFile);
> }//function
>
> //===>> Function drop_page_cache <<===
> function cache_drop($Item = NULL, $Args = NULL) {
>         global $CacheFilesRoot;
>
>         if (!defined('SECTION')) {$Section = 'UnSectioned';} else {$Section =
> strtolower(SECTION);}
>
>         if (!is_string($Item) || (!is_string($Args) && !is_array($Args)))
> {global $ErrMsg; func_error_handler(array(__FUNCTION__,
> func_get_args(), $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         $ArgsHash = args_hash($Args);
>
>         $Dir = $CacheFilesRoot."/".$Section."/".$Item;
>         $CacheFile = $Dir."/".$ArgsHash;
>
>         debug_log('Cache', __FUNCTION__."($Item, $ArgsHash) dropping cache");
> return file_delete($CacheFile);
> }//function
>
> //Do not forget to clear vars, before reusing
> function dir_size_atime_info($DirName = NULL) {
>         if (!is_string($DirName)) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>
>         static $FullSize = 0, $Amount = 0, $Id = 0, $Files = array();
>
>         //static vars has to be cleaned by hand
>         if ($DirName == 'CLEARVARS') {
>                 $FullSize = 0; $Amount = 0; $Id = 0; $Files = array();
> //              debug_log('Cache', __FUNCTION__.": variables cleared 
> [$FullSize,
> $Amount, $Id, ".str_replace("\n", '', print_r($Files, 1))."]");
>                 return TRUE;
>         }//if($DirName
>
>         if (!is_dir($DirName)) {return array('amount' => $Amount, 'size' =>
> $FullSize, 'data' => $Files);}
>
>         if (!$DirHandle = opendir($DirName)) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(), "$DirName
> {$ErrMsg['FileCantBeOpened']}")); return FALSE;}
>         while (FALSE != ($File = readdir($DirHandle))) {
>                 $FullFilePath = $DirName.'/'.$File;
>                 if (($File != '.') && ($File != '..') && 
> !is_link($FullFilePath)) {
>                         if (is_dir($FullFilePath)) {
>                                 dir_size_atime_info($FullFilePath);
>                         } else if(is_file($FullFilePath))  {
>                                 $FileStatInfo = stat($FullFilePath);
>                                 $Files['file'][$Id] = $FullFilePath; //file
>                                 $Files['size'][$Id] = $FileStatInfo['size']; 
> //size
>                                 $Files['atime'][$Id] = 
> $FileStatInfo['atime']; //access time
>                                 $Id++; $Amount++; $FullSize += 
> $FileStatInfo['size'];
> //                              debug_log('Cache', __FUNCTION__." full size: 
> $FullSize");
>                                 unset($FileStatInfo);
>                         }//if
>                 }//if
>         } //while (skip '.', '..' and links)
>         closedir($DirHandle);
>
> return array('amount' => $Amount, 'size' => $FullSize, 'data' =>
> $Files);
> }//function
>
> function cache_clean($SectionToClean = NULL) {
>         if (!is_string($SectionToClean)) {global $ErrMsg;
> func_error_handler(array(__FUNCTION__, func_get_args(),
> $ErrMsg['InvalidArgs'])); return FALSE;}
>         global $CacheSize, $CacheFilesRoot;
>
>         $FullPath = $CacheFilesRoot.'/'.$SectionToClean;
>
>         $NodeLimit = mbytes_to_bytes($CacheSize[$SectionToClean]);
>         $NodeCacheInfo = dir_size_atime_info($FullPath);
> //      debug_log('Cache', __FUNCTION__."() accepted
> {$NodeCacheInfo['amount']} files; size: {$NodeCacheInfo['size']}
> (".bytes_to_mbytes($NodeCacheInfo['size'])." Mb).");
>         dir_size_atime_info('CLEARVARS');
>
>         $Counter = 0;
>         $Odds = $NodeLimit - $NodeCacheInfo['size'];
>
>         if (($Odds <= 0) && ($NodeCacheInfo['size'] != 0)) {
>                 $Odds *= -1;
>                 debug_log('Cache', "[$SectionToClean] ".'Limit exceed for 
> '.$Odds.'
> bytes ('.bytes_to_mbytes($Odds).' Mb), '.$NodeCacheInfo['amount'].'
> elements to process (curr limit: '.bytes_to_mbytes($NodeLimit).' Mb,
> curr occuped: '.bytes_to_mbytes($NodeCacheInfo['size']).' Mb)');
>
>                 asort($NodeCacheInfo['data']['atime']);
>
>                 for (; $Value = current($NodeCacheInfo['data']['atime']), 
> $Odds > 0;
> $Value = next($NodeCacheInfo['data']['atime']), $Counter++) {
>                         $Key = key($NodeCacheInfo['data']['atime']); $Size =
> $NodeCacheInfo['data']['size'][$Key]; $FileName =
> $NodeCacheInfo['data']['file'][$Key];
>                         if (file_delete($FileName)) {$Odds -= $Size;}
>                         debug_log('Cache', "[$SectionToClean] Id: $Counter, 
> key: ".$Key." =>
> atime: $Value, size: $Size, file name: $FileName; odds: $Odds");
>                 }//for
>
>         } else {
>                 debug_log('Cache', "[$SectionToClean] ".'There is cache space
> ('.bytes_to_mbytes($NodeCacheInfo['size']).' Mb, limit:
> '.bytes_to_mbytes($NodeLimit).' Mb), no need to clean
> ('.$NodeCacheInfo['amount'].' files analyzed).');
>         }//if
>
>         clearstatcache();
>
> return $Counter;
> }//function
> ?>
> ==================================================================
>
> Hope it will be usefull and will find some reflection in existen CMSs.
> Please, feel free to ask me any questions regarding my CMS and such a
> cache implementation.
>
> Regards,
> /Alexander.
>
>


--
Diego F. Toritto.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-developers
-~----------~----~----~----~------~----~------~--~---

Reply via email to