Bukmaniac/Memcache
CS290F Fall 2006 - UCSB Computer Science - Thorsten von Eicken
BukManiac -- Your Search for Books ends here !
Home | Overview | Design | Critical Paths & Analysis | Optimizations | Scaling the Database | Scaling the Application | Plugging in the Cache
Contents |
Memcached
Memcached is a high-performance, distributed memory object caching system, generic in nature, but intended for use in speeding up dynamic web applications by alleviating database load . We used Memcached for caching search results.
Caching "Search Results"
We observed that a majority of the time was being spent on retrieving the Search results from the Database. Though , using MyISAM table for Full-Text search did help in enhancing the search performance, still the database was being hit hard when the load increased. So, we decided to cache the search results on basis of Search Query and Page number. We decided upon Fragment Caching and not Page caching since the we display user's name on the page and this, of course, changes on per user basis.
Further MemCache optimzation with EXTENDED FRAGMENT CACHE Plugin
Problem : We observed that the conventional Fragment caching nuilt in Rails with Memcached has a slight inefficiency : It requires 2 LOOKUPS TO THE FRAGMENT CACHE store to render a single cached fragment.Firstly, the read_fragment method invoked in a controller to determine if a fragment has already been cached. Secondly, the cache helper method invoked in a view that renders the fragment.
Solution
We used a Plugin called " Extended Fragment Cache" [1]. It accesses the cache ONLY ONCE for retrieving a cached fragment. This plugin adds an in-process cache that saves the value retrieved from the fragment cache store. The in-process cache has two benefits: 1. It cuts in half the number of read requests sent to the fragment cache store. This can result in a considerable saving for sites that make heavy use of memcached. 2. Retrieving the fragment from the in-process cache is faster than going to fragment cache store
Code Snippet
Controller : DisplayResults Action where we check if the search results have been cached or not.
def displayresults
@num = params[:pagenum]
num = @num.to_i()
unless read_fragment(:action => params[:keyword] , :action_suffix =>num )
## Creating new Fragment to cache this serach result
logger.info("ADDING TO CACHE ")
if(params[:searchtype] == 'author')
@book_searches = BookSearch.find_on_page_author(params[:keyword], num)
else
@book_searches = BookSearch.find_on_page_title(params[:keyword], num)
end
else
## Search result has already been cached
logger.info("TAKING FROM CACHCE ")
end
end
View : Displayresults
<% cache ( :action =>params[:keyword], :action_suffix => @num.to_i) do %>
---// Rendering books
<% end %>
Memcache Test Setup
We used the following EC2 instances for testing the performace using Memcache :
1. A web server with memcache daemon running on it
2. A Database instance
3. One or more App servers with environment.rb files properly configured to access the memcached server.
Results
Great improvements in the Response time and Reply Rate were observed by Caching the search results.
1. Response Time
This graph shows the effect of using memcached of fragment caching on the response time with scaling. We contrast the performace with the cache plugged in with a database of 270,000 records, a simple DB access with 270,000 records and 104,000 records. We observe an average performace benefit of 3 times.
2. Reply Rate
This graph shows the effect of using memcached of fragment caching on the reply rate handled by the server. We see an improvement of two times over the performace with a simple database containing 270,000 records.
Cache Invalidation
For invalidating the Cache, we considered the following scenarios :
1. Invalidating Cache on book's quantity = 0 : Our web design is such that we show the books irrespective of its quantity.However, when the user adds a book to his cart or checkout, quantity validation check is made with the Database. So, we did not require such type of invalidations.
2. Invalidating Cache on Time basis : This approach left us in a situation as to what should be the Expiry time of a cached fragment.So ,we dint find this approach suitable.
3. Invalidating Cache whenever a book is deleted : This was the best suitable scenario where the cached books will be invalidated on 'Delete book' event. However, according to the design that we chose to build memcache, we were caching 10 books in 1 fragment. So, it would be difficult to find a deleted book in the fragment cache store since fragments are being stored on the basis of search keyword and page number. One possible scenario would be to invalidate the whole cache when a book is deleted. But this doesnt sound logical.
Better Solution : We should have had created a fragment for each book on basis of its name and search keyowrd. Now, whenever a books is deleted , we could have searched in the Fragment cache store on the basis of its name and invalidate it.
Caching Sessions?!
Inorder to speed up the performace, we made efforts to cache the Sessions instead of keeping them in the database. The following results were observed:
Though there was a nice hike in the response time and reply rate , we discovered the following downside consequences :
- Random losing of session data. The error would go up from 0-30% when we increasd the rate from 1 - 60 with sessions = 100. A user could suddenly lose his cart in the middle of his actions which was not a very reliable idea. Henceforth, we decided upon using the Database for storing the Session.
