Cache SQL result into ElastiCache

2014.08.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

This post is English translation of “ElastiCacheにRDSへのクエリ結果をキャッシュさせる” written by @smokeymonkey.

Introduction

Amazon ElastiCache has several use cases, one of which is query caching. When application accesses Amazon RDS or other RDBMS, query result set is cached in ElastiCache. When application tries to read data, firstly queries it to ElastiCache, then queries to RDS only when the aimed object does not exist in ElastiCache.

I implemented a simple Ruby code to test this behavior.

Preparation

Today I used ElastiCache for Memcached, and RDS for MySQL, and Dalli, Memcached client written in Ruby.

Dalli’s usage is very easy. just Dalli.new to create connection definition and then get/set the target value.

$ irb
irb(main):001:0> require 'dalli'
=> true
irb(main):002:0> dc = Dalli::Client.new('smokeycache.xxxxx.cfg.apne1.cache.amazonaws.com:11211')
=> #<Dalli::Client:0x0000000115fb48 @servers=["smokeycache.xxxxx.cfg.apne1.cache.amazonaws.com:11211"], @options={}, @ring=nil>
irb(main):003:0> dc.set('foo','bar')
=> 144115188075855872

Let's check if the value is set to ElastiCache correctly by opening another terminal and query to Memcached.

$ irb
irb(main):001:0> require 'dalli'
=> true
irb(main):002:0> dc = Dalli::Client.new('smokeycache.xxxxx.cfg.apne1.cache.amazonaws.com:11211')
=> #<Dalli::Client:0x00000002073a08 @servers=["smokeycache.xxxxx.cfg.apne1.cache.amazonaws.com:11211"], @options={}, @ring=nil>
irb(main):003:0> puts dc.get('foo')
bar
=> nil

That’s all.

Implementation

The Ruby code below is a sample code to cache SQL Result into ElastiCache for Memcached. SQL string is passed by command line arguments. The code removes spaces from SQL string, serializes using Marshal, encodes the result into Base64. This result is to be used as the key of Memcached record. If cache hits, the result is fetched from Memcached. If not, query is issued toward MySQL, and the result of it is recorded into Memcached.

require ‘mysql’ 
require ‘dalli’ 
require ‘base64’ 
 
dc = Dalli::Client.new(‘smokeycache.xxxxx.cfg.apne1.cache.amazonaws.com:11211’) 
value = Array.new 
 
sqlstr = ARGV[0] 
key = Base64.encode64(Marshal.dump(sqlstr.strip)) 
 
if dc.get(key) == nil then 
  puts “*** no cache ***” 
  mysqlcli = Mysql.connect(‘dbs.xxxxx.ap-northeast-1.rds.amazonaws.com’,’user’,’password’,’shop’) 
  mysqlcli.query(ARGV[0]).each_hash do |row| 
    puts row 
    value.push(row) 
  end 
  dc.set(key, value) 
else 
  puts “*** cache hit ***” 
  value = dc.get(key) 
  value.each do |row| 
    puts row 
  end 
end

Run the code.

The table “customer” is like below:

mysql> select * from customer;
+------+----------+
| id   | name     |
+------+----------+
|    1 | alice    |
|    2 | bob      |
|    3 | charles  |
|    4 | donny    |
|    5 | elie     |
|    6 | fabian   |
|    7 | gabriel  |
|    8 | harold   |
|    9 | Ignatius |
|   10 | jonny    |
+------+----------+
10 rows in set (0.00 sec)

At first, get all rows of customer table.

$ ruby ./cache.rb “select * from customer” 
*** no cache ***
{“id”=>”1”, “name”=>”alice”} 
{“id”=>”2”, “name”=>”bob”} 
{“id”=>”3”, “name”=>”charles”} 
{“id”=>”4”, “name”=>”donny”} 
{“id”=>”5”, “name”=>”elie”} 
{“id”=>”6”, “name”=>”fabian”} 
{“id”=>”7”, “name”=>”gabriel”} 
{“id”=>”8”, “name”=>”harold”} 
{“id”=>”9”, “name”=>”Ignatius”} 
{“id”=>”10”, “name”=>”jonny”}

As you can see, “no cache” is printed as the Memcached currently have no cache at all. Try once more time.

$ ruby ./cache.rb “select * from customer” 
*** cache hit ***
{“id”=>”1”, “name”=>”alice”} 
{“id”=>”2”, “name”=>”bob”} 
{“id”=>”3”, “name”=>”charles”} 
{“id”=>”4”, “name”=>”donny”} 
{“id”=>”5”, “name”=>”elie”} 
{“id”=>”6”, “name”=>”fabian”} 
{“id”=>”7”, “name”=>”gabriel”} 
{“id”=>”8”, “name”=>”harold”} 
{“id”=>”9”, “name”=>”Ignatius”} 
{“id”=>”10”, “name”=>”jonny”}

“cache hit” is printed, which mean Memcached returned the query cache record.

Next, try another SQL statement.

$ ruby ./cache.rb “select * from customer where id = 3” 
*** no cache ***
{“id”=>”3”, “name”=>”charles”}

“no cache” is printed because this SQL is not recorded as cache.

$ ruby ./cache.rb “select * from customer where id = 3” 
*** cache hit ***
{“id”=>”3”, “name”=>”charles”}

you can see “cache hit” again!

Summary

In this case, I guess the benefit of cache is limited as I tried with very small amount of data. However, as the amount of data grows, ElastiCache for Memcached works effectively as cache I feel like getting well with ElastiCache :)