ElastiCacheにRDSへのクエリ結果をキャッシュさせる

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

はじめに

ElastiCacheの用途として、RDSと併用して、RDSへのクエリ結果をElastiCacheにキャッシュさせ、

  • キャッシュがある場合はElastiCacheのキャッシュを使う
  • キャッシュが無い場合のみRDSにDBアクセス

という使い方があります。これはアプリケーションエンジニアの皆さんには一般的なロジックなのでしょうが、実は僕にはいまいちピンと来ていないところがありました。

そこで、実際にRubyで実装して、その動きを確認してみたい!と思ったのが今回のスタート地点です。

AWS_Design_Untitled_-_Cacoo

準備

ElastiCacheはMemcachedエンジンで構築しました。またRDSはMySQLで構築しています。

RubyからMemcachedエンジンのElastiCacheを操作する方法として、Dalliを用いました。

$ gem install dalli

使い方はすごく簡単で、Dalli::Client.newで接続定義を作り、dc.setで値をセットし、dc.getで値を取得するだけです。とりあえず値をセットしてみます。

$ 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

別のコンソールから値を取得してみます。

$ 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

これだけです。

実装してみる

引数として渡されたSQLから空白を除去し、marshalでシリアライズしてBase64エンコードしたものを、keyとして扱います。このkeyがElastiCacheにあればキャッシュからデータを取り出し、無ければRDSに接続してSQLを投げて結果を取得、そしてその結果をElastiCacheに登録します。

[Ruby] 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 [/Ruby]

やってみる

customerテーブルの内容はこんな感じです。

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)

それではまず全件取り出してみます。

$ 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"}

"no cache"と表示されたので、RDSから取得されたことが分かります。もう一度やってみます。

$ 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"と表示されたので、ElastiCacheから取得していますね!次にSQL文を変更してみましょう。

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

前回と違うSQL文なので、"no cache"と表示されました。もう一度やってみます。

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

"cache hit"しました!

まとめ

実際に手を動かしていることで、ElastiCacheの使い方が良く分かりました。今回のような少量のデータだと差異はほとんど出ませんが、大量のデータであったり、コストの高いSQLであった場合は、効果的なのでしょう。

そんなわけで、ElastiCacheとちょっと仲良くなれました:)