GameZone/Caching with memcached

CS290F Fall 2006 - UCSB Computer Science - Thorsten von Eicken

Revision as of 11:01, 14 December 2006; Keshava (Talk | contribs)
(diff) ←Older revision | Current revision | Newer revision→ (diff)
Jump to: navigation, search

Home | Overview | Architecture | CriticalPath | Optimizations and Analysis | Performance on multiple servers | Caching | Conclusion


Contents

Caching Sessions with memcached

We used memcached to cache our sessions and our results. Thereby we have reduced the load on the database. We did get some performance benefit by storing the cache in memcached there were issues storing sessions persistantly in the cache, we chose to store them in the DB itself. Here is snapshot of sessions being served form the cache

<41 get gz_mem-production:session:8f5c8f716e61e1ab32180753d8129d92
>41 END
<41 set gz_mem-production:session:8f5c8f716e61e1ab32180753d8129d92 0 1800 61
>41 STORED

The memcached was run on the web server itself as it was very lightly loaded.

Here is a graph of the performace by using caches in sessions

Image:gz_memcache_replyrate.JPG Image:gz_memcache_reqrate.JPG

The test was conducted with nearly 115K records in the DB

Fragment caching with memcached

We have used fragment caching to store our store general catalog listing and our search results.

We also cached our search results by appending our search query and search page number to the object being cached. the performance numbers were lukewarm since database access were not so expensive themselves due to small database size. Further, network latencies play a major factor as they are also of comparable order of magnitude. Further investigation is necessary to say with confidence, how much "real" performance gain is achieved by the use of memcached.

Here are our results with fragment caching

Image:GZ_replyrate3app.JPG Image:GZ_reqrate3app.JPG

Here is the code modifications that we needed to do to achieve page caching in our listing.

  • Controller:
 def list
     @current = params[:page]
    logger.info("paramspage " + params[:page].to_s)
   unless read_fragment(:action =>"list", :cpage => @current)
       logger.info(">>>>>>>>>>>>>>>>>>>>>>  Hitting DB Now!! " + Time.now.to_s)
       @products_pages, @products = paginate :products, :first_n => 500, :conditions => ['numberavailable > 0'] , :per_page => 10
   else
       logger.info("********************* Served data from cache ")
   end
 end
  • View:
  <% cache(:action => "list", :cpage=> @current) do %>
     #Expensive database access goes here...
  <%end%>

Two read_fragment() per page access!!!

When we wish to access certain data from the DB, the controller first checks if the data is available by doing a read_fragment() from memcached. The memcached returns the object if it exists. after this checking, the controller short-circuits the code around the actual read from the database and simply returns. The cache() code block does the same in the view and does a read_fragment again (internally). This causes 2 reads for every page that is served from the cache, which has an impact on the performance.

We looked around for a while and found the Extended Fragment Cache Plugin here. After plugging it in, we were able to bring the accesses down to one :) In the author's own words, "Retrieving the fragment from the in-process cache is faster than going to fragment cache store. On a typical dev box, the savings are relatively small but would be noticeable in a standard production environment using memcached (where the fragment cache is often running on another machine)"

Cache invalidation

We considered three possible approaches

  • Invalidate based on time: We considered times based invalidations of our caches. Several interesting questions came out our mind
    • What is the right amount of time to invalidate a cache?
    • Should it be a static value or something that is linked to number of requests received
  • Invalidations based on Database writes
    • Should caches be invalidated when the content they hold expire? This is hard to do as we are caching a full page with 10 records (as a rendered web page) with almost no control over the actual contents in them.
  • New Approach : Finally, we concluded that the best way is to keep the views totally clean and handle all the caching at the controller using the write_fragment and read fragment. This provides for greater control of the whole caching process. This is, in effect a trade off. Two points are worth mentioning here.
    • If the database is the bottleneck, and the DB changes frequently, it is worth doing all the caching in the Controller. In this method, the rendering time does not change in any way, but smart management of the DB cache invalidation is possible.
    • If the DB does not change that infrequently, it is worth doing the "cache() do" in the view to cache the HTML code and save on rendering time with explicit (yet limited) work by the controller.

For results of performance of servers with caching, please go GameZone/Performance on multiple servers