A rant about proper memcache usage
March 13, 2009I have been noticing an interesting pattern for a while now, and figured it was about time to help set the story straight on memcache. If you don’t know what memcache is, I recommend you reading up on it first.
Memcache is a dead simple cache and it works very well when you remember that it is a simple secondary level to your data. Developers tend to forget this, which leads to some very interesting consequences.
What memcache is.
I can not stress enough that memcache is a very simple cache and works best when you treat it just like that and nothing more. Its ultra fast because it does that 1 simple thing very well. Adding even minor things like delete in x number of seconds adds overhead that defeats the purpose of it.
Don’t get me wrong, without some more complex features memcache can be pretty difficult at times. Things like pagination and key organization are some of the biggest problems.
What memcache is NOT!
- Its NOT a database – Don’t treat it as your primary database. MySQL is designed to protect and organize your data.
- Its NOT a queue system – Think about what you are trying to do. You are putting important flags into a system that is not guaranteed to have your data. Can you deal with your queue missing performing work a few times ?
- Its NOT intended for session handling – Similar to the reason above would you want users to randomly get booted out ? Don’t get me wrong, you want something fast .. I understand. But, there are alternatives such as MCache. Keep in mind, I have not actually used it. It has an ultra annoying build system. Before you think of using memcache for sessions realize what the impact on your users is. Wouldn’t you be annoyed if you were checking your E-Mail on Gmail and got booted randomly ?
- Its NOT a proxy – If you are serving files or cached dynamic HTML you should strongly consider using varnish or mod_proxy. If you are experiencing issues with io load on your server and you want to serve files very fast you could also consider using TempFS. Remember to populate your data on boot time
- If you need replication you might be using memcache wrong. If you are looking for replication for memcache step back and think why do I really want this ? There is a pretty good chance you don’t have a fall back plan. The same holds true for wanting to create a backup of your memcahe to restore in the event of a node failure or reboot. There is a never a good reason to do this.
- Its NOT a locking daemon – Don’t get me wrong, there aren’t many alternatives but this is just not an option. Never forget, memcache is never guaranteed. But with that in mind, you could use it as a locking daemon but only if you can deal with duplicate runs.
Best practices
- Use connection pools, don’t get crafty with your connections unless you FULLY understand what you are doing. Running multiple instances and separate connection handlers is not the proper way to handle things. Don’t use 127.0.0.1 when you are running multiple instances. Use the external IP and specify your entire pool on EACH webserver.
$memcache = new memcache;
$memcache->addServer(’192.168.1.1′,11211);
$memcache->addServer(’192.168.1.2′,11211);
$memcache->addServer(’192.168.1.3′,11211);
- Always have a fall back. If the data isn’t found in memcache, go to your datastore. That doesn’t mean that you need to exit execution of your site if the datastore isn’t reachable. One of my sites can run with 85% of the features when MySQL is unreachable. Basically, anything that requires a write will not function and is disabled. I use warm up scripts that pre-populate memcache during each build. Its a simple script that uses my existing code to populate, severally lowering the maintenance requirements. I set some randomness to the TTLs so they don’t all expire at the same time. This would cause sudden spikes to your database.
- Use low cache times until stable. Until you are properly cache busting in your application keep your cache times very low. This will allow you to pinpoint problems, get a little bit of performance and not piss off your visitors too much.
- Create a wrapper or extend your memcache library, especially in PHP. This will allow you to create stastics during development and troubleshoot key management. Not to mention, create an “internal cache” that prevents duplicate over the wire requests.
- Cache negative search results, lets say you have user profile pages and you ban a user. There might be a high amount of traffic looking for that record that doesn’t exist in your database (well, if you actually delete the data
). Prevent those look ups from hitting your database and you will be happier for it.
- Don’t md5() your keys! This really drives me nuts. Why on earth would you want to do this?!? There are rare times when your key name could contain things that violate the memcache protocol. However, to my knowledge pretty much every library will strip out violation characters. If they don’t strip these out, it could lead to command injection. Doing things like this make memcached verbose (-vv) pretty worthless. Not to mention, a lot of calls to md5() just chew up CPU cycles. Sure, with modern processors its very quick but this is a huge problem these days. No one wants to create tight and solid code, they just want it done yesterday.
- Go easy on calling memcache for stats. If you are running Cacti on your memcache farm go easy on how much you poll. Don’t hit it every minute as after you have your memcache setup working well you won’t watch those graphs all the time. Your graphs will reach a point where they don’t really move too much… Unless for some reason you are doing frequent memcached restarts.
- Use increment / decrement for things like number of views on a page. After you run an update query, why bother running another select query right after that? Depending on your storage engine, this could cause lock contention. Instead, run the update and use memcache::increment(). This will help performance greatly since you are just publishing your data, not trying to process it.
- Don’t use a shotgun to take down a spider. Lets say you have a box on your home page that shows the last 10 posts. Don’t set the cache key for 10 minutes, set it for 2+ hours and create a cron job that performs the select and updates memcache. This will also reduce lock contention, you don’t want all those vistors fighting to update the same key. Remember that during development, you will want to keep that expiration low and increase it to hours when you consider it stable.
- Use memcache during development. This is another one that baffles me. It drives me crazy when developers won’t develop with memcache enabled. You need to really test this. Test your CRUD (Create Read Update Delete) and few times over and for multiple users. If you are not using your production strategies during development, you are essentially writing code that is untested (and in some ways could even be considered a fork of your app for lack of functionality)
- Running memcache verbose (-vv) during development helps a lot. You can quickly see when variables aren’t being set properly. Imagine seeing a key like user_ scroll by, you can spot problems quick.
I have a few follow up articles that I have planned that will touch on more advanced usage of memcache. One of these updates will include the memcache wrapper that I am using for my projects.






Spot on Joe. Good article! Now it you’ll excuse me, I need to go get a .22 for that spider…
This is a really great post on memcache best practices. It’s pretty much everything I’ve learned the hard way, particularly connecting using the external IP. That finer point will actually be causing me some pain later this month…
[...] things Memcached is not include a database, a queue system and a [...]
Good points. Instead one: I still use md5 for keys. Would love to read what you suggest instead.
My keys need to include method name + arguments passed, so I am using something like:
md5( serialize( array( method name, array( arguments passed ) ) ) ) as memcache key.
This keeps my keys unique and short (as serialized arguments might be longish sometimes). What would you suggest instead of md5 in that case?
Depending on the number of arguments you are passing, you might want to consider listing out each parameter manually in the key name. For example: mykey_$sort_$order_$limit_$value1_$value2_$other
Its also much easier to perform a delete against a key like that in another part of your code. Unless you have a method for every key you create, which is kinda overboard.
Not to mention, it would also be much harder to understand what user_c4ca4238a0b923820dcc509a6f75849b is vs user_c81e728d9d4c2f636f067f89cc14862c when looking at memcached -vv or other debug tools.
It takes a little more work, but its all about key organization.
Number of arguments vary – this is a huge app with hundreds of business logic methods (and business layer is cached in the way described). Sometimes arguments are array of values. I cannot put that into key name as plain text – I need to make an unique hash somehow.
I do not delete cache entries and don’t need to understand what particular key stands for. Cache entries have short expire of 60 seconds and that’s enough in terms of deletion. All I am concerned of is performance = how much I could gain riping off md5 and what to chose instead. Thanks
I am in QA and was wondering what is the best way to test if memcached is correctly working on our dev code. I have a situation where we cache all the books for all the authors. The developer says that if the response time is fast enough then memcache is working. The QA in me says that I should profile the database (MSSQL), which I did, and I noticed a few stored proc calls (which retrieve the books title). If caching was working properly there should have been no trip to this stored proc. I would say about 15-20% of the call end up missing the cache and go to database.
The developer says that memcached might have some issues and there is nothing he can do about it.
Any suggestions on what to do?
Thanks
Fantastic write up and rant
It makes me despair when these basic rules get thrown out the window!
We did however just have to write a backup for memcached…
We have a site which for client requirements reasons absolutely has to run off Oracle, but the site itself makes a huge amount of queries on the homepage. If a change is made and the homepage cache has to be emptied the load put on Oracle by the volume of traffic hitting the page takes down the Oracle server. To get around this, we run a queue which replaces keys in Memcached in the background, rather than deleting the cache and letting a user repopulate it with a hit.
Our problem though is if one of the memcached servers goes down for any reason such as power failure or any general failure. Because the users no longer rebuild the cache, there is no way to rebuild the lost content. Instead what we are doing is piping the memcached log through a deamon we have written which scans for PUTs and when a PUT is done it does a GET on that key and saves it out to an SQLlite database. This effectively gives us a background running mirror of each memcached instance. Then on boot, the contents of the SQLlite table is loaded back into memcached very quickly.
An ugly and unfortunate but effective solution to a painful problem.
[...] http://joped.com/2009/03/a-rant-about-proper-memcache-usage/ [...]
What I cannot find is documentation on what the verbose output actually means. For example, I have the following:
<set topnav-US-EN—new,first_visit,everytone-0 2050 4477 2256
Then later I see the same thing except the numbers are 0,2050 4438, 2256 (the middle number changes)…. I recognize my key from my set statement… what does the rest of it mean? I can't seem to find any info on that.
-Mark
This is a repost of my response to you on the memcache mailing list. I figured I would also include it here for those interested.
The being the response. topnav-US-EN—new,first_visit,everytone-0 is your key. I don’t remember what 2050 is. 4477 is your key expiration time. I also don’t know what 2256 means.
I’d recommend reading the text protocol docs, they can explain it in further detail.