Welcome!

Open Source Authors: Liz McMillan, Maureen O'Gara, Jeremy Geelan, Reuven Cohen, Lavenya Dilip

Related Topics: Open Source, PHP

Open Source: Article

Put Your PHP App on Steroids

Optimizing with APC: Alternative PHP Cache

It’s really easy, so don’t fret. APC is free, and it’s a PECL extension, so there’s no need to re-compile PHP. What’s more, the default settings are pretty darn optimal, so there’s really no need to tweak the configuration once you’ve got it up and running. Keep in mind, since you’re going to be caching everything in memory, you’re going to need some memory to spare. So, don’t install this on a server with 256 megs of RAM, but don’t go thinking you need to run out and get something with 4 gigs of RAM either. You can get by just fine with a gig or so (even 512 if you want), and that’s a pretty standard amount you get with most dedicated / VPS setups these days. Anyway, rather than going into the actual details of installation, just check out the PHP page for APC. It’s got everything you need, including links to installing PECL extensions: www.php.net/apc.

Now, assuming you’ve got everything installed and set up, let’s get busy looking at how we can use APC in our own code to make things more gooder. Let’s pretend you have an application that fetches some news to be displayed on a home page, and this page gets a lot of traffic, but the news isn’t updated all that often (maybe once or twice a day). You’ve probably got something that looks like this to fetch all that:

function getNews() 
{
     $query_string = 'SELECT * FROM news ORDER BY 
date_created DESC limit 5'
; $result = mysql_query($query_string, $link); .... return $newsThatYouParsed; }

Let’s take a look at what we could do to have APC store that $result in memory so we don’t have to hit the database constantly for relatively static data. A good way to think about caching is to treat it like an array. You’re going to store and retrieve data by using a key, the same as you would in an array. The only difference is that we’ve also got to decide how long we want to store this data in cache. This should all make a bit more sense once you take a look at the following code, which defines a class that we can use to interact with the cache (and we’ll later use in our getNews() example):

class CacheManager
{
     public function fetch($key)
     {
          return apc_fetch($key);
     }
 
     public function store($key, $data, $ttl)
     {
          return apc_store($key, $data, $ttl);
     }
 
     public function delete($key)
     {
          return apc_delete($key);
     }
}

Pretty straight-forward, right? Everything should make sense to you, except perhaps the $ttl. This is the number of seconds we’d like the value to stay in cache (or 0 to keep it until we explicitly remove it)… it’s also short for “time to live”. Anyway, let’s assume that you’ve included this class definition somewhere else in your app, and you now want to use it to speed up your news function. Well, we need to decide what key to use when we store this data, and this decision depends on whether or not other functions might be using this cached data as well. This key needs to be unique enough that you won’t accidentally overwrite it elsewhere in your code, and easy enough to fetch without doing a ton of processing. Think about it this way: you might have a query that gets run often (maybe fetching a user’s name from the database to display on every page), but it’s slightly different for each visitor (everything’s the same, except perhaps a user id, or date range). There’s a really easy way to deal with this issue, and rather than explain it, I’ll just implement it in my above getNews() example:

// make sure the cache class is included somewhere
function getNews() 
{
     $query_string = 'SELECT * FROM news 
ORDER BY date_created DESC limit 5'
;   // see if this is cached first... if($data = CacheManager::get(md5($query_string))) { $result = $data; } else { $result = mysql_query($query_string, $link); $resultsArray = array();   while($line = mysql_fetch_object($result)) { $resultsArray[] = $line; }   CacheManager::set(md5($query_string),
$resultsArray, 3600); // whatever
TTL is right for you
}   .... return $newsThatYouParsed; }

Pretty neat, huh? By simply using an MD5 hash of the query string, we’re pretty reliably guaranteed to have a unique key for that dataset, and we don’t need to write a ton of extra logic to remember that key in the future. But what do you do if the news is updated before the cache expires? Well, it depends on whether or not it needs to be super-fresh. Once the item expires (assuming the ttl wasn’t zero), the cache will be refreshed. This is nice because essentially only one user will ever cause a request to the database for your news per hour (in our example). Every other user will be seeing the cached results. No more database bottleneck! If you need to make that cache update whenever news is added, regardless of expiration, just add some code to your update functionality. Say you had an update function, here’s what you could do to make it update the cache and the database:

function updateNews($newsData)
{
     $query_string = 'UPDATE news SET ...';
     mysql_query($query_string, $link);
 
     // this is really basic, and you should do 
something much more elegant
$query_string = 'SELECT * FROM news ORDER BY
date_created DESC limit 5'
; $result = mysql_query($query_string, $link); $resultsArray = array();   while($line = mysql_fetch_object($result)) { $resultsArray[] = $line; }   CacheManager::set(md5($query_string),
$resultsArray, 3600); }

Why not just call “getNews()”? What if it had an echo statement in there? Wouldn’t look so good having your news appear randomly every time you updated it, would it?

Anyway, that’s a real quick look at how easy it is to super-charge your app with a little bit of extra code, and one cool PHP extension. In my personal experience, it does make a huge difference with high-load applications, but it does have one hitch: this is only good if your app runs on one server. If you have multiple servers, the cache can’t be shared… not with APC at least.

Which brings me to the closer…

Very soon, I’ll write about what you can do if you need to have, and use, a shared cache across many servers (it’s actually the same technology the folks at Facebook use, and was invented to speed up livejournal). In the meantime, take a few minutes to play around with APC… I think you’ll come to the same conclusion that I have: It’s pretty damn bad-ass.

More Stories By Ian Selby

Ian Selby is owner of Gen X Design. He has been developing web sites professionally for over 7 years, and has always tried to stay on the cutting edge of web trends and technologies. He believes that usability and the user experience are the most important thing when writing web applications. He has a passion for all things Web 2.0, loves shiny icons and gradients, and lives in the San Francisco Bay area. He works for Aptana.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.