Home > kvs
kvs Archive
memcached vs TokyoCabinet vs TokyoTyrant vs Redis
NoSQLの話題が周りで盛り上がっているので、有名どころのライブラリのset/getのベンチマークを取ってみることにしました。対象はmemcached, TokyoCabinet, TokyoTyrant, Redisの4種類。gemで入れたruby用のバインディングを利用して1000件のデータをset, getしています。結果はこんなかんじ。
| user | system | total | real | |
|---|---|---|---|---|
| memcached:set | 0.120000 | 0.030000 | 0.150000 | ( 0.213069) |
| memcached:get | 0.150000 | 0.030000 | 0.180000 | ( 0.238989) |
| TokyoCabinet:set | 0.000000 | 0.000000 | 0.000000 | ( 0.002802) |
| TokyoCabinet:get | 0.000000 | 0.000000 | 0.000000 | ( 0.001759) |
| TokyoTyrant:set | 0.010000 | 0.000000 | 0.010000 | ( 0.005384) |
| TokyoTyrant:get | 0.030000 | 0.000000 | 0.030000 | ( 0.038285) |
| Redis:set | 0.040000 | 0.030000 | 0.070000 | ( 0.147060) |
| Redis:get | 0.040000 | 0.020000 | 0.060000 | ( 0.151168) |
当然のごとくmemcachedが最速だろう。。。と思いきや、そうでもない結果に。むしろ一番遅い結果に。なんだこれーーーと思って調べ続けていたのですが、バインディングのgemのコードを追いかけるかぎり、どうもこれはmemcache-clientの実装が原因のよう。
これは、memcache-clientの実装はpure-rubyで実装されているのに対して、TokyoCabinet/TokyoTyrantのバインディングの実装はnativeコードで実装されてあるのが原因のようです。事実、TokyoTyrantはmemcacheプロトコルを実装しているので、memcache-clientを利用してTokyoTyrantにアクセスすると両者はこんな結果になりました。
| user | system | total | real | |
|---|---|---|---|---|
| memcache:set | 0.120000 | 0.040000 | 0.160000 | ( 0.189803) |
| memcache:get | 0.150000 | 0.030000 | 0.180000 | ( 0.240141) |
| TokyoTyrant:set | 0.130000 | 0.030000 | 0.160000 | ( 0.238009) |
| TokyoTyrant:get | 0.130000 | 0.020000 | 0.150000 | ( 0.271598) |
TokyoTyrantのアクセス速度は一気に遅くなりました。やっぱりRuby実装に引きづられているみたいですね。。
ちなみに、Redisもsetに限るとmemcacheプロトコルと同じプロトコルを利用できるので、そのベンチをとってみると
| user | system | total | real | |
|---|---|---|---|---|
| Redis:set | 0.040000 | 0.010000 | 0.050000 | ( 0.091300) |
と、なってmemcacheクライアントを利用したときのmemcached, TokyoTyrantと比較しても圧倒的に高速だということがわかりました(おもしろい!)
まとめ
- オンメモリだからmemcachedが高速、とは限らない
- memcache-clientは遅いので注意
- 各ライブラリのオリジナルのバインディングを利用した場合、高速順に並び替えるとTokyoCabinet, TokyoTyrant, Redis, memcachedになる
と、いうわけで、memcachedが高速なことを確認するためにベンチマーク取ってみたのですが、実装によってはまったく異なる結果になることが分かって、なかなかおもしろかったです。なお、今回利用したコードはgistに掲載しています。
redisでユーザをfollowしたときにTimeLineをsortして再構築
前回のつづき。
課題の1つに挙げてた中で、「Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない」というのがありましたが、こんなかんじでいいのかな。自分のTLに他人のTLを混ぜて、sortしてstoreし直し。(redis-rbが0.2.0じゃないと動かないかも)
def merge_timeline(user_id)
my_id = @login_user[:id]
return if @redis.type?("uid:#{user_id}:posts") == "none"
user_statuses = @redis.lrange("uid:#{user_id}:posts", 0, 100)
user_statuses.each do |status|
@redis.lpush("uid:#{my_id}:home", status)
end
@redis.sort("uid:#{my_id}:home", :order => "desc alpha", :store => "uid:#{my_id}:home")
end
でも、そうしたあとにremoveしたときにまた再構築し直さないと駄目なことに気づいた。。と、いうかremove面倒ですねぇ、どうするんだろう。泥臭く全statusを走査し直しなのか、それとも順序付Setとか使えば楽にremoveできるのかな。また考え直しです。
あと、このupdateについて、github上のRedTweetのコードもpushし直してますので、興味ある方はご確認ください。
redisとRailsでTwitterクローン「RedTweet」を作りました
前回「Mac OSXにredisをインストール」で、redisを動かす環境まではできたので、せっかくなんでテスト的に何かサービスを作ってみよう、ということでTwitterクローンのRedTweetを作ってみました。
redisを使ったTwitterクローンは、PHP版のRetwisと、それをSinatraで書き直したRetwis-RBがあるのですが、サンプルコードはいくらっても世の中に少しは役立つだろうと思ってRails版で実装してみました。オンラインで動作できる環境はないので、git cloneしてscript/serverで手元の起動で確認ください、、と投げやり気味ですみません。とりあえず次の項目は一通り実装しています。
- ユーザID発行
- Login / Logout
- Follow / Remove
- 自分のTimeline, Public Timeline, 各ユーザのTimelineの閲覧
ちなみにRedTweetって名前はRedisとTweetを混ぜて直感でつけた名前で、git pushしたあとで同じ名前のサイトがあることが発覚したくらい直感でつけた名前です。
目的
さて、今回はまじめにTwitterクローンを作ることが目的ではなくて、実際は、次の項目を目的として実装してみました。
- Retwisのデザインを読んで、それに従って一通り実装してみる
- redisのAPIの仕様を学ぶ
- RDBを一切使わない、NoSQLでWebサービスを作るためのノウハウを身につける
結局全て似通った話になるのですが、上記のデザイン仕様書はTwitter的なサービスを作り上げることで、KVSをどのように利用すればいいのか、がかなり分かりやすく説明されてあるので、いい勉強になりました。また、ユーザ情報はString, 各TLはList, Following/FollowerをSetで管理することで、redisの主要なAPIを網羅できたことも、redisの学習に役立ちました。
と、同時に課題もすでに見えていて、
- スケーリングがどこまでできるかはまだ手元で理解できていない
- やっぱりActiveRecord的にラップしたライブラリは必須
- Followした瞬間に、そのユーザの過去のTweetを自分のTLに追加できていない
なんかが今の段階で挙げられます。1.はテストデータを作ることで解決するはず。2.はやっぱりmustだなぁ、と思えるところまでははっきりした理解で、ユーザ名をIDから引くときなど毎回決まったprefixがついたkeyから探索するのは冗長すぎてやってられません。OhmというHashとObjectのマッピングライブラリもあるので、このあたりも1度使ってみたほうが良さそうだな、というところ。3.はFollowした瞬間に一定数TweetをListにLPUSHして、そのlistの中でSORTすればいいのかな??正直、SORTまだよくわかってません。
まとめ
というわけで、課題は多いものの、redisとRailsで最低限の動作をするものは実装できました。NoSQLでもサービスを作り上げることは理解できたので、Ohmなど、他のライブラリも使ったりすることで、 redisの利点をもっと伸ばして理解を深めたいな、というところですね。
Mac OSXにredisをインストール
Tokyo Cabinet / Tokyo Tyrantに代表されるKey-Value Storage,いわゆるKVSは多くのプロダクトが乱立してなかなか違いを理解するのも難しい状況になりつつあるのですが、ここ数日はredisに注目をしています。redisとは何ぞやというと、公式サイトには次のような特徴が挙げられています。
- memcachedのようないわゆるKVS
- valueにはStringだけではなく、List, Setも利用できる
- データに対するpush/pop, add/removeのような操作はすべてatomicな操作
- 通常はメモリ上で動作するが、定期的にデータをディスクに書き出す(常に書き出すような設定も可能)
- Master-Slaveのレプしケーションをサポート
- Ruby, Python, Perl, Java...などの各言語のバインディングを用意
と、かなり豪華なスペックで、なんでもかかってこい!なプロダクトです。国内の利用例はまだ聴いた事無いですが、海外ではgithubやCraigslistが採用したことでここ最近有名になってきました。
さて、そんなredisの開発環境を整えてみたいと思います。環境はMac OSX 10.5。
redisのインストール
ソースコードから入れてもいいのですが、Macの場合は、MacPortsでインストール可能です。
$sudo port -d sync
で、インストールリストを最新の状態にしておいて
$sudo port install redis
で、インストールされる、、はずなのですが、僕の環境だと途中で止まりました。
$ sudo port install redis ---> Fetching redis ---> Attempting to fetch redis-1.2.2.tar.gz from http://redis.googlecode.com/files/ ---> Verifying checksum(s) for redis ---> Extracting redis ---> Configuring redis ---> Building redis ---> Staging redis into destroot ---> Creating launchd control script ########################################################### # A startup item has been generated that will aid in # starting redis with launchd. It is disabled # by default. Execute the following command to start it, # and to cause it to launch at startup: # # sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist ########################################################### ---> Installing redis @1.2.2_0 ---> Activating redis @1.2.2_0 Error: Target org.macports.activate returned: couldn't open "/opt/local/var/log/redis.log": no such file or directory Error: Status 1 encountered during processing.
なんかlogディレクトリがないみたいなので、ディレクトリ作成してから一度アンインストールしてやりなおしてみます。
$ sudo mkdir /opt/local/var/log $ sudo port uninstall redis $ sudo port install redis ================================================================== * To start up a redis server instance use this command: * * redis-server /opt/local/etc/redis.conf * ================================================================== ---> Cleaning redis
これで、インストールできました。サービスに登録したい場合は、途中のログに書いてあるとおり、
sudo launchctl load -w /Library/LaunchDaemons/org.macports.redis.plist
で、OKです。
Rubyのバインディングのインストール
僕は普段Rubyを使うので、Rubyのバインディングもあわせてインストールしてみることにします。gemでインストール可能です。
sudo gem install redis
redisの起動
redisを起動するときは、redis-serverを実行します。MacPortsでインストールした場合、/opt/local/bin にバイナリがインストールされてあるので
sudo /opt/local/bin/redis-server /opt/local/etc/redis.conf
で、起動します。confの中には、起動するときのポート番号や、ディスクに書き出すタイミングや条件などの設定項目があります。(ちなみに、起動時に設定ファイルのパスを与えていますが、これはビルド時?に設定しておけば、実行時に与える必要は無いようです。ただ、MacPorts利用時もそれが可能なのかどうか?は不明です)
では、実際に動作を確認してみたいとおもいます。確認するときは、redis-serverと同じ場所にあるredis-cli, またはtelnetで6379に対して接続してコマンドを入力します。(今回はtelnetで接続して生のコマンドを叩いて確認してみます)ValueをStringとして扱うときは、
set
set foo 3 bar +OK get foo $3 bar
listとして扱うときは、lpush
lpush artists 8 fishmans +OK lpush artists 9 clammbon +OK lpush artists 7 polaris +OK lrange artists 0 -1 *3 $7 polaris $8 clammbon $8 fishmans
Listの要素追加もStringの要素追加と同じでO(1)で実現できるのがredisのポイントですね。実際の細かなコマンドの仕様については、リファレンスを参照ください。
ここまでざっと環境構築まで確認できたので、次回はもうすこしredisを使い倒してその結果をまとめたいと思います。
Home > kvs