はじめてのRedis

いわゆるNoSQLがRDBMSを完全に駆逐することはたぶんないと思うけど、NoSQLの登場によってMySQLでは実現し難かった機能がつくれるんじゃないか、と思う今日このごろ。なのでNoSQLプロダクトは1つずつ触ってみて、特徴を把握することが大事だと思ってる。ということでまずはRedis。

インストール

MacBook Airにインストールなので、Homebrewで。

$ brew install redis

あとはインストール後に案内された通りに、下記コマンド実行すればセットアップ完了。

$ mkdir -p ~/Library/LaunchAgents
$ cp /usr/local/Cellar/redis/2.4.8/homebrew.mxcl.redis.plist ~/Library/LaunchAgents/
$ launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.redis.plist

データ型

文字列

文字列型はRedisで扱う型の中で最も基本的なものです。Redis文字列型はバイナリセーフです。つまりRedis文字列型はどんな種類のデータも保持できるということです。たとえばJPEGイメージやシリアライズされたRubyオブジェクトなども持つことができます。

文字列は最大1GBまで扱うことが出来ます。

文字列型は INCR コマンド群からは整数値として扱われます。この点において、整数値は符号付き64bit値に制限されます。

Redisリスト型、Redisセット型、Redisソート済みセット型、Redisハッシュ表型で保持される各要素はRedis文字列型であることを覚えておいてください。

http://redis.shibu.jp/commandreference/strings.html

リスト

Redisリスト型はRedis文字列型のリストになっています。Redisリストには新しい要素をリストの先頭(左側)または末尾(右側)に追加することが可能です。

Redisリストは双方向リストで実装されています。両端(先頭または末尾)から必要な要素にアクセスするために双方向リストで実装されていることによって、いくつかのコマンドではその恩恵を受けています。 LRANGE や LINDEX といったコマンドがその例です。
双方向リストを使うことによって、リスト長にかかわらずプッシュやポップがO(1)の操作になることが保証されています。

http://redis.shibu.jp/commandreference/lists.html

セット

Redisセット型はRedis文字列型の順不同の集合です。Redisセット型ではメンバの追加、削除、確認をすべてO(1)で行うことができます。

Redisセットは幅広い操作をサポートしています。たとえば結合、共通部分、差異の取得などです。共通部分の取得は参照回数が最小限になるように最適化されています。たとえば10000要素あるセットと2要素のセットの共通部分を取得しようとした場合、Redisは2要素を持つセットをイタレートしてもう一方のセットにそれぞれの要素が存在するかの確認をします。10000要素の参照ではなく2要素のみの参照で済むわけです。

Redisセットはハッシュ表を使って実装されていまので、メンバの追加、削除、確認といった操作は平均でO(1)となります。ハッシュ表は新しい要素が追加または削除されたときに自動的にリサイズします。

http://redis.shibu.jp/commandreference/sets.html

ソート済みセット

Redisソート済みセット型はRedisセット型とよく似ていて、Redis文字列型の集合となっています。違いはソート済みセットのすべてのメンバはスコアに関連したハッシュ値を持っています。元となっているスコアを用いてメンバを順番に並べます。


Redisセット型はスキップリストとハッシュ表の2つのデータ構造を用いて実装されています。要素が追加されたときには要素とスコアの間のマッピングがハッシュ表に追加されます。(これによって要素を用いてスコアを取得する操作がO(1)で行われます)そしてスコアと要素の間のマッピングはスキップリストに追加され順番に並べられます。

Redisは双方向リストを使っています。特別なスキップリストの実装をしています。理由は、逆順に捜査出来るようにするためです。

http://redis.shibu.jp/commandreference/sortedsets.html

ハッシュ

Redisハッシュ型は順番がないRedis文字列型のフィールドと値のマップです。フィールドの追加、削除、確認をならしてO(1)で行うことができます。すべてのキー、値、またはその両方を一覧するのはO(N)で行うことができます。(Nはハッシュ内のフィールドの数です)

Redisハッシュ型は面白い作りになっています。どのような点が面白いかというと、オブジェクトを表現するのにとても適した形になっているところです。例えば、ウェブアプリケーションユーザはたとえばユーザ名、暗号化されたパスワード、最終ログイン時刻などのフィールドを持ったRedisハッシュで表現されます。

http://redis.shibu.jp/commandreference/hashes.html

memcachedとの違い

例えば、最新投稿のうちn〜m番目までを取得したいというケースで考えてみる。

memcached

データ構造がサポートされてないので、クライアントでシリアライズ/デシリアライズする。ある程度の範囲を丸っと保存しておき、クライアント側で配列のn〜m番目にアクセスする。

Redis

データ構造としてサポートされているリスト型を使ってデータを保存しておく。LRANGEを使って、n〜m番目を取り出す。


クライアントライブラリに優劣なければ、Redisのほうがプログラムしやすそう。

使ってみる

$ redis-server
[1022] 12 Jun 08:30:23 # Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf'
[1022] 12 Jun 08:30:23 * Server started, Redis version 2.4.8
[1022] 12 Jun 08:30:23 * DB loaded from disk: 0 seconds
[1022] 12 Jun 08:30:23 * The server is now ready to accept connections on port 6379

でサーバを上げて

$ redis-cli
redis 127.0.0.1:6379>

でクライアントを上げる。

文字列型
redis 127.0.0.1:6379> set perl 1
OK
redis 127.0.0.1:6379> set ruby 2
OK
redis 127.0.0.1:6379> get perl
"1"
redis 127.0.0.1:6379> get ruby
"2"
redis 127.0.0.1:6379> mget perl ruby
1) "1"
2) "2"
redis 127.0.0.1:6379> incr ruby
(integer) 3
リスト型
redis 127.0.0.1:6379> rpush my_list foo
(integer) 1
redis 127.0.0.1:6379> rpush my_list bar
(integer) 2
redis 127.0.0.1:6379> lpush my_list piyo
(integer) 3
redis 127.0.0.1:6379> llen my_list
(integer) 3
redis 127.0.0.1:6379> lrange my_list 0 3
1) "piyo"
2) "foo"
3) "bar"
セット型
redis 127.0.0.1:6379> smembers language1
 1) "objective-c"
 2) "lua"
 3) "ruby"
 4) "python"
 5) "java"
 6) "basic"
 7) "c"
 8) "perl"
 9) "scalar"
10) "c#"
11) "javascript"
12) "lisp"
13) "php"
14) "c++"

redis 127.0.0.1:6379> smembers language2
1) "perl"
2) "php"
3) "ruby"
4) "python"

redis 127.0.0.1:6379> sinter language1 language2
1) "perl"
2) "php"
3) "ruby"
4) "python"

redis 127.0.0.1:6379> sdiff language1 language2
 1) "objective-c"
 2) "java"
 3) "basic"
 4) "lua"
 5) "c"
 6) "c#"
 7) "scalar"
 8) "javascript"
 9) "lisp"
10) "c++"
ソート済みセット型
redis 127.0.0.1:6379> zrevrange exam 1 5 withscores
 1) "rucy"
 2) "91"
 3) "mike"
 4) "82"
 5) "tom"
 6) "80"
 7) "bill"
 8) "77"
 9) "steve"
10) "73"

redis 127.0.0.1:6379> zrevrank exam rucy
(integer) 1
redis 127.0.0.1:6379> zrevrank exam mike
(integer) 2
ハッシュ型
redis 127.0.0.1:6379> hgetall my_hash
1) "foo"
2) "1"
3) "bar"
4) "2"
5) "piyo"
6) "3"

redis 127.0.0.1:6379> hkeys my_hash
1) "foo"
2) "bar"
3) "piyo"

redis 127.0.0.1:6379> hvals my_hash
1) "1"
2) "2"
3) "3"

ベンチマーク

http://d.hatena.ne.jp/tullio/20101103/1288798289
単純なget/set速度比較だと、memcachedとRedisでは前者に軍配があがるようです。が、データ構造のサポートがRedisのウリだと思うので、Redis遅いから使えねー、という話ではないですね。解こうとしている問題に対して、適材適所なのかが大事。(注: ベンチマークをdisりたいわけではない)

まとめ

Redisのデータ型について調べて、それぞれについて軽く触ってみた。リスト型やソート済セット型は何かと便利そうなので、MySQLやmemcachedが苦手とする領域にRedisが使えないかな?と考えるのがいいかもしれない。