Share on Facebook subscribe feed

Stateful Event Driven Caching with Cache_Lite, PHP and APC

The problem with all caching systems is that they are based on timeout values.  Although these can help tremendously to reduce load on a database server, this design does not have the ability to respond to changing data on the backend, often resulting in "Stale Caches" that are displaying information that is out of date.

This is because in timeout based caching, the only variable that determines whether or not a cache file is valid or not is its age.  No other considerations are taken into effect.

The TON 2.0 Platform relies heavily on both database query caching as well as full-on page caching (which, in our architecture we call the "SuperView").
What is Caching?

In a nutshell, code, processes, database calls, etc. are time consuming.  On a big website, with lots of information on each page, you may need many database calls or expensive "data retrieval" calls to gather all of the information that is displayed on the page.

But - The information is not always changing, which makes 90% of your database calls redundent (they always produce the same result).

Caching - takes all of the information that was pulled from the database, stores it as pure text, then just delivers the text on all future requests which takes the database out of the picture.

Long story short - Caching speeds up your website - greatly.

 

With our system constantly changing with new photos, new articles, new videos and talk forum posts and comments, too often our users were experiencing delays in their information appearing on the website(s) after posting.  This then resulted into duplicate posting because they assumed their post didn't go through, etc.

Many websites use the technique of deleting the cache files on the back end when new posts get added, etc.  However, in our situation, we have so many cachable components that depended on various different queries, it became impossible to track and know which cache files to actually delete.

In order to remedy this problem, we had to come up with a way for the system to understand the changing state of the database, and then be able to respond to the changes by invalidating cache files when the state of the system changed.


Our goals were the following:

1.  Allow a cache file to expire based on "state" attached to that cache file
2.  Allow the system to evaluate the system state with no hits to the database
3.  Release our system as open source as we believe other developers may be able to take advantage of this

In order to accomplish this we developed an extension to the popular Cache_Lite package that could track and respond to events.

We call our extension Stateful_Cache which is just a special class that extends Cache_Lite with additional functionality. (download Stateful_Cache)

In addition to the Stateful_Cache extension, we also developed custom event handlers and observers that use APC to track system state in-memory.

How does it work?

In a nutshell, Stateful_Cache writes a special header to every cache file prior to saving it that records a list of functions and values that can be used later to evaluate the state of the cache file.

When the cache file is retrieved by Stateful_Cache, the functions are evaluated, and if any of them return false, the cache file is invalidated.

In our implementation, we store our Stateful_Cache functions in a special file that is included when we create an instance of our stateful cache object.


Example #1 - Timeout Caching per Cache File
A common request we have seen is the ability to store different timeouts in the cache files themselves, without relying on one global timeout value.  The advantage of this is your controlling script can read and process full-page cache files, without loading any other code, and the cache files can tell the controlling script whether or not they are valid.
//File:  scachefunctions.php
/**
 * This file contains our Stateful Cache Evaluation functions that are loaded at run time
 * We precede all function names with "SC_" which the Stateful_Cache class with automatically pre-pend to the function prior to eval'ing it.
 */

/**
 * Returns true if the timeout value is still valid
 * @param int $timer - the time the cache file was written
 * @param int $timeout - the timeout value in seconds
 * @return bool true if our timeout value is not expired
 */
function SC_CacheExpire($timer,$timeout)
{
if((time()-$timeout)>$timer)
return(false);
else
return(true);
}

The above code is contained in our include file "scachefunctions.php".  The "SC_" portion of the functionname is important, as we use this for security reasons at runtime when evaling the header
//File  index.php
/**
 * Sample file that demonstrates the use of Stateful_Cache
 */

//ASSUMPTIONS  -  the variable $pageData contains all of the HTML Code that will be written to the page

include_once("scache_Lite.php");

$scache=new Stateful_Cache();  

$scache->registerStateFile("scachefunctions.php");  //Load the state 

$pageId="MyHomePage";

$pageData=$scache->get($pageId);   //the get method will automatically read the stateful cache header that was set previously

if(!$pageData)
{
    //The cache is invalid, regenerate the page
   //....
   //...
   //...A bunch of code here that creates the $pageData string with all of the HTML....e.g. your call to your template engine, etc.
   //.....

 //Now we write the cache file and tell it to use ourr CacheExpire function which accepts 2 arguments, the current time() and our timeout value
 //  We are creating a string version of the function call, which could look something like "CacheExpire(123456789,300)"

  $scache->registerState("CacheExpire(".time().",300)");

  $scache->save($pageData,$pageId); 

}

//Either we pulled from cache, or we generated the data again, write it to the page

echo $pageData;



 


The Stateful Cache Header
In the above example, when the cache file is written, the CacheExpire function is written to the header of the file.  In raw form, the header looks like this:
!Stateful_Cache!CacheExpire(12345678,300);!Stateful_Cache_End!
 
The get method of Stateful_Cache will check for the existence of this header, and then  evaluate the functions.  Special note that the special pre-pend "SC_" is placed at the beginning of the function prior to making the actual function call.

To ensure the function get's called, the registerStateFile method loads our include file that contains our function(s).



Example #2 - Tracking Database State with APC
This is where we really get the bang for our buck in this system.   Using APC (the Alternative PHP Cache) we are going to store system state, in memory, across the entire application.   

We will use a very simple time integer based system that will change values based on events that occur.

//File:  scachefunctions.php
/**
 * This file contains our Stateful Cache Evaluation functions that are loaded at run time
 * We precede all function names with "SC_" which the Stateful_Cache class with automatically pre-pend to the function prior to eval'ing it.
 */

/**
 * Returns true if the Forum Sate value is still valid
 * @param int $state - the state integer variable
 * @return bool true if our $state value matches the state stored in apc
 */
function SC_ForumState($state)
{
$apcState=apc_fetch("forum_state");

        if($apcState==$state)
             return(true);
       else
            return(false);
}

The above function simply compares the value of the the Forum State ("forum_state") stored in memory, to the value passed to the function from our cache file
 
//File  index.php
/**
 * Sample file that demonstrates the use of Stateful_Cache and APC 
 */

//ASSUMPTIONS  -  the variable $pageData contains all of the HTML Code that will be written to the page

include_once("scache_Lite.php");

$scache=new Stateful_Cache();  

$scache->registerStateFile("scachefunctions.php");  //Load the state 

$pageId="MyForumsPage";

$pageData=$scache->get($pageId);   //the get method will automatically read the stateful cache header that was set previously

if(!$pageData)
{
    //The cache is invalid, regenerate the page
   //....
   //...
   //...A bunch of code here that creates the $pageData string with all of the HTML....e.g. your call to your template engine, etc.
   //.....

 //Now we write the cache file and tell it to use ourr CacheExpire function which accepts 2 arguments, the current time() and our timeout value
 //  We are creating a string version of the function call, which could look something like "CacheExpire(123456789,300)"

  $forumState=apc_fetch("forum_state");  //get the current value stored in memory

  $scache->registerState("ForumState(".$forumState.")");

  $scache->save($pageData,$pageId); 

}

//Either we pulled from cache, or we generated the data again, write it to the page

echo $pageData;



 


The above code will write the current state of the forums to the cache file header prior to saving.  This will then be evaluated and compared to the state stored in memory everytime the cache file is read....

Updating the State is handled by the code that processes new forum posts or comments
//Code here that accepts a new forum post
///The forum post is added to the database
//Then - we update the state!

//In this example, we will just store a timestamp of the last time a forum topic was added...

apc_store("forum_state",time());

 

By using this method, the timestamp of the last new topic is always stored in memory.  This way, when a cache file is read and the header is evaluated, if the forum_state in the cache file does not match the forum_state stored in memory, the cache file will be invalidated.


Additional Notes
When saving a cache file, you can register as many Stateful Cache functions as you want to using registerState();   For instance, if your home page will change everytime a new photo or article is added, then you just need to create the state functions to track those variables, and write them to the cache file.  Anytime either of the values change, the home page cache file will be invalidated and regenerated.

Serialized Objects
Stateful_Cache even works with objects that will be serialized (such as arrays, class objects, etc.).  It handles this by writing the header to the cache file prior to saving but AFTER serialization.  When reading the file, the Stateful Cache header is stripped off BEFORE unserializing.

Function Names
You can obscure your included (and eval'd) function names by using your own custom pre-pend string.   This string is stored in the "_stateFunctionPrePend" property of Stateful_Cache (Stateful_Cache::_stateFunctionPrePend).  This defaults to "SC_" but can be any string

Header String Code
Further secure your cache files by using a customer header code / name.  This defaults to "Stateful_Cache" but can be any string and is stored in the "_statefulCacheHeader" property of Stateful_Cache (Stateful_Cache::_statefulCacheHeader);


Getting Stateful Cache

Download Stateful Cache from the TON Open Source Pages.

Comments, suggestions or improvements
Feel free to comment on this post, or email me at eric@midkotasolutions.com


Eric Kubischta
Midkota Solutions and the TON 2.0 Core Platform Team
Bismarck, ND

















Posted By:
Posted On: 08/05/2009 11:03 AM
267 Views, 0 Comments

Tags: cache, caching, stateful, lite, apc, timeout, event, php, driven, database
More Tags: Caching, database server, PHP, PHP, Technology_Internet
Region: Global

Categories: Hunting > Other Hunting
Rate This Article
  • Currently 5/5
  • 1
  • 2
  • 3
  • 4
  • 5

5/5 (1 votes cast)



You must be signed in to comment on this Article





Total Outdoor Network
TON Sites | Contact | Advertise | Privacy Policy | Terms of Use



North Dakota Fishing and Hunting | Minnesota Fishing and Hunting | Wisconsin Fishing and Hunting | Nebraska Fishing and Hunting | Walleye Fishing | South Dakota Fishing and Hunting | Alaska Fishing and Hunting |