読者です 読者をやめる 読者になる 読者になる

まめ畑

ゆるゆると書いていきます

ElastiCacheとELBとtwemproxy

redis memcache AWS

redis / memcachedをスケールする方法として、アプリケーションで分散アルゴリズムを実装する方法や、ライブラリを使う方法などありますが、
Twitterが作っているtwemproxy(https://github.com/twitter/twemproxy)というものがあります。
これは、redis / memachedの前段に置くことでキャッシュクラスタを構成することが出来ます。様々な分散アルゴリズムや、故障ノードの切り離しなどの機能もあり、
キャッシュノードが不具合で接続できなくなったとしても自動でサービスアウトしてくれます。
開発も盛んに進んでいて、今、ノード追加時にプロセスの再起動が必要ですが、gracefulの実装も見えて来ました。
詳しくは以前書いたこちらの記事を参照して下さい。http://d.conma.me/entry/20121227/1356596553


twemproxyは現状だとノード追加時にプロセスの再起動が必要なので、その間通信が切れてしまいます。
また、冗長構成をとるためにELBの配下に置くことで、切り離しつつ設定ファイルを書き換えていったり、twemproxy自体の障害にも対応出来るようになります。
そこで、ElasatiCacheだけ・twemproxy+ElastiCacahe・twemproxy+ElastiCacahe+Internal ELB の3種類の構成でパフォーマンステストを行いました。


ElastiCacheはcache.m1.large、twemproxyはm1.largeを使用しました。
10万キーのget / setを行う時間を計測しcmd/secを出しています。並列度は100ノード、キーサイズは40bytes / valueは400 bytesです。
横軸はElastiCacheノード数です。

ElastiCacheだけ

f:id:con_mame:20130122192631p:plain

twemproxy+ElastiCacahe

f:id:con_mame:20130122192634p:plain
f:id:con_mame:20130122192632p:plain

twemproxy+ElastiCacahe+ELB

f:id:con_mame:20130122192630p:plain
f:id:con_mame:20130122192633p:plain

結果

ELBを挟むことで桁が1つ下がる結果となりました。そして、各ノード数に関係なく粗一定の性能になりました。
twemproxyを使用した事による性能の劣化は殆どなく高速に動作しています。しかし、CPU使用率がcmd/secが上がるにつれて多くなる点に注意が必要です。
分散アルゴリズムはtwemproxyが対応しているものを全て試したのですが、殆ど差は見受けられない結果となりました。

以前、DBの前段にInternal ELBを配置してパフォーマンスを計測したのですが、ここまでの性能劣化は起こらなかったので、さらに詳しく検証をしています。
その時は、接続したらセッションはそのままになっていたのですが、今回はset / getの度にセッションを切断していたので、それが原因かと思っています。

ElastiCacheも1つのendpointに接続して、config get cluster を発行すると、有効なノードの接続情報を返してくれますが、クライアントの対応が必要です。

ElastiCache側でkeyによる分散が実装されるとさらにElastiCacheの使用が快適になりますが、今のところそのような話は出ていないっぽいので、このようなproxyを使うといいのではないかなと思います。
twemproxyが2台であれば、HeartBeatを使用して、ENIの付け替えでactive-standby構成が作れます。

おまけ

ELBでtwemproxyのヘルスチェックを行う際には、管理ポートがデフォルトでは22222番ポートになっているので、こちらを見るか、twemproxyの各ノードグループのlisten portでもいいと思いますが、ノードグループのノード数が基準値以下になったら切り離すというような場合は以下のような簡単なスクリプトを配置してxinetdでアクセスを受け付ける事で実現出来ます。

require 'json'
require 'net/telnet'

SEVER_CONFIG_NAME = 'elastic_cache'  # 監視を行うノードグループ名
SERVER_REGEX = /^twemproxy-test\.9jpctq\..*/  # ElastiCacacheのサーバ名の正規表現
MIN_SPEAR_SERVER_NUM = 2  #  # 最低限組み込んでおくサーバ数
SERVICE_IN_SERVER_NUM = 3 # サービスインしているcache node数
TWEMPROXY_IPADDR = '127.0.0.1'  # twemproxyの動いてるノードのIPアドレス
TWEMPROXY_ADMIN_PORT = 22222 # 管理ポート

def is_healthy?
  begin
    info = ''
    telnet = Net::Telnet.new("Host" => TWEMPROXY_IPADDR, "Port" => TWEMPROXY_ADMIN_PORT)
    telnet.cmd('') { |c| info = c }
    telnet.close
    ejected_server_count = JSON.parse(info)[SEVER_CONFIG_NAME]['server_ejects'].to_i
  rescue Exception => e
    return false
  end
  return false if ejected_server_count.nil?
  (SERVICE_IN_SERVER_NUM - ejected_server_count) >= MIN_SPEAR_SERVER_NUM
end

gets

if is_healthy? and not File.exists?('/tmp/out')
  code_msg = "200 OK"
else
  code_msg = "503 Service Unavailable"
end

print "HTTP/1.0 #{code_msg}\r\n"
print "Content-type: text/html\r\n"
print "Content

これを適当な名前で保存して、/etc/xinet.d/twemproxyとかに以下の記述をします

service twemproxy
{
        flags           = REUSE
        socket_type     = stream
        port            = 2525
        wait            = no
        user            = nagios
        group           = nagios
        server          = /usr/bin/twemproxy-check
        log_on_failure  += USERID
        disable         = no
        only_from       = 127.0.0.1 10.0.0.0/16
        per_source      = UNLIMITED
        instances       = UNLIMITED
}

これで、Internal ELBのヘルスチェックは、twemproxyが動いているサーバに

Protocol: http
Port: 2525
Path: /

で監視が出来るようになります。
また、/tmp/out でファイルを作ると任意のタイミングでELBから切り離す事ができます。

更に高負荷環境で検証をしているので、まとめたいと思います。

twemproxyの設定

elastic_cache:
  listen: 0.0.0.0:22121
  hash: md5
  distribution: ketama
  auto_eject_hosts: true
  redis: false
  hash_tag: "{}"
  backlog: 4096
  preconnect: true
  timeout: 400
  server_retry_timeout: 5000
  server_failure_limit: 1
  servers:
   - twemproxy-test.9jpctq.0001.apne1.cache.amazonaws.com:11211:1
   - twemproxy-test.9jpctq.0002.apne1.cache.amazonaws.com:11211:1
   - twemproxy-test.9jpctq.0003.apne1.cache.amazonaws.com:11211:1
   - twemproxy-test.9jpctq.0004.apne1.cache.amazonaws.com:11211:1