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

Amazon ElastiCache

この記事は公開されてから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とちょっと仲良くなれました:)

  • ga

    マスタテーブルに対し、これでいいと思う。トランザクションテーブルに対して、レコードが更新されたら、CacheとDBの同期しないと問題がなる

  • http://www.smokeymonkey.net/ すもけ

    gaさん>
    コメントありがとうございます!
    仰る通り、今回はただデータをpush/pullしましたが、expires_in: => 600など、キャッシュに対してTTLを設定して有効期間を過ぎたものはキャッシュを破棄するなどが必要になるかと思います。