I recently had a run-in with a very popular PHP ecommerce package which makes me want to voice a recurring mistake I see in how many web applications are architected.
What is that mistake?
The ecommerce package I was working with depended on caching. Out of the box it couldn’t serve 10 pages/second unless I enabled some features which were designed to be “optional” (but clearly they weren’t).
I think with great tools like memcached it is easy to get carried away and use it as the mallet for every performance problem, but in many cases it should not be your first choice. Here is why:
- Caching might not work for all visitors – You look at a page, it loads fast. But is this the same for every user? Caching can sometimes be an optimization that makes the average user have a faster experience, but in reality you should be caring more that all users get a good experience (Peter explains why here, talking about six sigma). In practice it can often be the same user that has all the cache misses, which can make this problem even worse.
- Caching can reduce visibility – You look at the performance profile of what takes the most time for a page to load and start trying to apply optimization. The problem is that the profile you are looking at may skew what you should really be optimizing. The real need (thinking six sigma again) is to know what the miss path costs, but it is somewhat hidden.
- Cache management is really hard – have you planned for cache stampeding, or many cache items being invalidated at the same time?
What alternative approach should be taken?
Caching should be seen more as a burden that many applications just can’t live without. You don’t want that burden until you have exhausted all other easily reachable optimizations.
What other optimizations are possible?
Before implementing caching, here is a non-exhaustive checklist to run through:
- Do you understand every execution plan of every query? If you don’t, set long_query_time=0 and use mk-query-digest to capture queries. Run them through MySQL’s EXPLAIN command.
- Do your queries SELECT *, only to use subset of columns? Or do you extract many rows, only to use a subset? If so, you are extracting too much data, and (potentially) limiting further optimizations like covering indexes.
- Do you have information about how many queries were required to generate each page? Or more specifically do you know that each one of those queries is required, and that none of those queries could potentially be eliminated or merged?
I believe this post can be summed up as “Optimization rarely decreases complexity. Avoid adding complexity by only optimizing what is necessary to meet your goals.” – a quote from Justin’s slides on instrumentation-for-php. In terms of future-proofing design, many applications are better off keeping it simple and (at least initially) refusing the temptation to try and solve some problems “like the big guys do”.