ElastiCacheにRDSへのクエリ結果をキャッシュさせる
はじめに
ElastiCacheの用途として、RDSと併用して、RDSへのクエリ結果をElastiCacheにキャッシュさせ、
- キャッシュがある場合はElastiCacheのキャッシュを使う
- キャッシュが無い場合のみRDSにDBアクセス
という使い方があります。これはアプリケーションエンジニアの皆さんには一般的なロジックなのでしょうが、実は僕にはいまいちピンと来ていないところがありました。
そこで、実際にRubyで実装して、その動きを確認してみたい!と思ったのが今回のスタート地点です。
準備
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とちょっと仲良くなれました:)