Efficient Memcached Usage
Lessons learned while implementing memcached on zvents.com
Tyler Kovacs
First and Foremost
- Typical memcache operation takes a fraction of a millisecond (for a local instance of memcached). Realistically, memcached optimization is one of the last things you'll need to do. Start with the low-hanging fruit which will have a much larger effect: optimize your database, enable page, action and fragment caching, etc.
- But that's no excuse not to plan ahead.
Get The Latest Software
- What versions are you running?
- memcached
- The memcached NEWS page implies that there haven.t been any releases since 2004.
- Make sure you're running latest memcached (1.2.0 released 09/2006)
- Lots of CPU and memory consumption fixes committed in 05/2006
- Ruby client libraries
- Run MemCacheClient instead of RubyMemCache API
- gem install memcache-client
- Make sure you're on latest version (1.2.0 as of 11/2006)
Watch Your Footprint
- run memcached -vv
- Analogous to watching SQL statements in development.log
- How often do you touch the cache per page request? You might be surprised.
- On a busy site, cache touches can be a bottleneck
- How many hits can memcached handle?
- Lots - benchmark it yourself. Here's a best-case benchmark:
CACHE["a"] = 1
n = 10000
get_total = Benchmark::measure{ for i in 1..n; CACHE["a"]; end }.real
set_total = Benchmark::measure{ for i in 1..n; CACHE["a"] = rand; end }.real
puts "#{n} GET requests in #{get_total} seconds: #{n/get_total} req/sec"
puts "#{n} SET requests in #{set_total} seconds: #{n/set_total} req/sec"
Watch Your Footprint (more)
- Things will be slower in production
- Caching more complex data types (ActiveRecord objects) that have higher marshaling costs
- memcached probably running on separate hosts (network overhead)
Memcached Statistics
- memcached collects useful performance and utlization statistics
| pid | uptime |
| time | version |
| rusage_user | rusage_system |
| curr_items | total_items |
| curr_connections | total_connections |
| connection_structures | cmd_get |
| cmd_set | get_hits |
| get_misses | bytes_read |
| bytes_written | limit_maxbytes |
| bytes | |
- Refer to protocol docs for explanation: http://www.danga.com/memcached/apis.bml
Performance Monitoring
- Use Cacti (or something similar) to monitor statistics over time
- or roll your own using stats command from memcache-client_extensions
Plugins
- Zvents Tools
- memcache-client_extensions
- Adds 3 commands to memcache-client API
- get_multi: retrieve multiple keys in parallel
- stats: return memcached performance and utilization statistics
- flush_all: empty all information stored in the cache
extended_fragment_cache (more)
- Content Interpolation
- Easily substitute dynamic values into cached data
- Reduces complexity in views
- Reduces cache touches
- We can replace this:
<% cache("/first_part") do %>
This content is very expensive to generate, so let's
fragment cache it.
<% end %>
<%= Time.now %>
<% cache("/second_part") do %>
This content is also very expensive to generate.
<% end %>
extended_fragment_cache (more)
<% cache("/only_part", {}, {"__TIME_GOES_HERE__" => Time.now}) do %>
This content is very expensive to generate, so let's
fragment cache it.
__TIME_GOES_HERE__
This content is also very expensive to generate.
<% end %>
also integrates time-based fragment caching from memcache_fragments plugin
<% cache("/only_part", {:expire => 15.minutes}) do %>
This content is very expensive to generate, so let's
fragment cache it.
<% end %>
custom_benchmarks
- Allows you to log arbitrary information to the summary line in the Rails default log
Completed in 0.07425 (13 reqs/sec) | Rendering: 0.02631 (35%)
| DB: 0.02668 (35%) | Search: 0.00000,0 (0%)
| memcache: 0.00073,2 (0%)| PID: 27605 | 200 OK
[http://zvents.dyndns.org/events/show/355761?nowsr=1]
Next release includes this default integration with memcache-client