redis 使ってますか?

redis という KVS 知っていますか?

自分は名前は知ってはいるけど ... 程度の認識だったのですが、新しいサーバを買った際、んーやっぱり社内で簡単に VM 上げたり下げたり出来る環境が欲しいなぁと思っていたところ @shibukawa から OpenStack いいよという話を聞いてドキュメントを呼んでいたら Redis を使っていると書いてあったので、へーと興味津々になって調べてみたら ...

メイン開発者2名は VMware がスポンサーになってフルタイムで redis の開発をしているというわけです。こらなんとまぁと。
そして色々ドキュメントを呼んでいたらなかなか素敵な KVS で、自分が欲しい KVS にたどり着いた感じです。

redis - Project Hosting on Google Code

魅力

日本語訳

redisドキュメント日本語訳 redis v2.0.3 documentation

なによりも日本語訳が存在します。これが最大の魅力でしょう。

  • @shibukawa
  • @ymotongpoo
  • @atusi

上記3名の有志が翻訳を行ってくれています。興味ある人は声をかけてみてください。

高速

redis の魅力はオンメモリ DB なのでとにかく早いということです。

永続化

定間隔ごとにファイルにデータを落としてくれるスナップショットという機能があります。
常にファイル保存して欲しい人もいますよね、この人のために追記専用という機能があります。これはほぼ毎秒事にログファイルにデータを記録して言ったりすべてのコマンドをログファイルに追記していったりする機能です。

永続化データからの復旧もとても早いです。簡単に試したところ 100 万件のデータで 15 秒程度で復旧しました。

様々なデータタイプ

通常 KVS は Key/Value の形で使いますが Redis はかなり違います。5つのタイプが存在し、それは同じデータベースに混在することが可能です。

  • Key/Value の文字列型
  • Key/List のリスト型
  • Key/Set のセット型
  • Key/SortedSet のソート済みセット型
  • Key/Hash のハッシュ型

上記の五つが存在します。

それぞれの使い方に対しては別途説明します。

レプリケーション

redis はデータを簡単にレプリケーションすることが可能です。
redis.conf にマスターの IP と Port を設定するだけでレプリケーションが行われます。

1000 万件のデータが 20 秒程度でレプリケーションされるとの事です。

ライセンス

ライセンスが修正 BSD で配布されていることも個人的には魅力の一つです。

ANSI C

redis は ANSI C で書いてあることもあり LinuxSolarisMac OS でも何も困らずビルドすることが可能です。
ビルドが簡単だというのはとても素敵なことだと思います。

redis-py から使ってみる

redis を使う各言語のバインディングはとてもたくさんあります。ここでは redis-py を使って redis の機能を紹介していきます。

string
% ipython -cl
Python 2.7 (r27:82500, Jul 12 2010, 08:24:10) 
Type "copyright", "credits" or "license" for more information.

IPython 0.10.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.
>>> import redis
>>> client = redis.Redis()
>>> client.set('voluntas', 20)
True
>>> client.get('voluntas')
'20'
>>> client.set('shibukawa', 30)
True
>>> client.mget(['voluntas', 'shibukawa'])
['20', '30']
>>> client.incr('shibukawa')
31
>>> client.exists('shibukawa')
True
>>> client.keys()
['shibukawa', 'voluntas']
>>> client.expire('shibukawa', 3)
True
>>> client.get('shibukawa')
'31'
# 3 秒過ぎてから
>>> client.get('shibukawa')
>>> client.randomkey()
'voluntas'
lists

key/value の value が list で追加できます。

>>> client.lpush('moriyoshi', 10)
1
>>> client.llen('moriyoshi')
1
>>> client.rpush('moriyoshi', 20)
2
>>> client.lrange('moriyoshi', 0, -1)
['10', '20']
>>> client.lpop('moriyoshi')
'10'
>>> client.lrange('moriyoshi', 0, -1)
['20']
>>> client.rpush('moriyoshi', 20)
2
>>> client.rpush('moriyoshi', 20)
3
>>> client.rpush('moriyoshi', 20)
4
>>> client.lrange('moriyoshi', 0, -1)
['20', '20', '20', '20']
sets

key/value の value が sets で入るためユニークが保証されます

>>> client.sadd('tokibito', 10)
True
>>> client.scard('tokibito')
1
>>> client.sadd('tokibito', 20)
True
>>> client.sadd('tokibito', 10)
False
>>> client.sadd('feiz', 10)
True
>>> client.sismember('tokibito', 10)
True
>>> client.sismember('tokibito', 50)
False
>>> client.sdiff(['feiz', 'tokibito'])
set([])
>>> client.sinter(['feiz', 'tokibito'])
set(['10'])
>>> client.sunion(['feiz', 'tokibito'])
set(['10', '20'])
sortedsets

key/value の value の中に key/value があって value をソートした結果で取得できます

>>> client.zadd('bucho', 'tokibito', 10)
True
>>> client.zadd('bucho', 'wozozo', 20)
True
>>> client.zadd('bucho', 'ae35', -30)
True
>>> client.zrange('bucho', 0, -1)
['ae35', 'tokibito', 'wozozo']
>>> client.zrank('bucho', 'tokibito')
1
>>> client.zrank('bucho', 'wozozo')
2
>>> client.zrevrank('bucho', 'wozozo')
0
>>> client.zrevrange('bucho', 0, -1)
['wozozo', 'tokibito', 'ae35']
>>> client.zcard('bucho')
3
>>> client.zscore('bucho', 'wozozo')
20.0
hashes

ハッシュ型です。Python で言うところの辞書

>>> client.hset('pyspa', 'kuenishi', 20)
1
>>> client.hset('pyspa', 'ymasuda', 30)
1
>>> client.hset('pyspa', 'mitszo', -10)
1
>>> client.hget('pyspa', 'kuenishi')
'20'
>>> client.hgetall('pyspa')
{'kuenishi': '20', 'ymasuda': '30', 'mitszo': '-10'}
>>> client.hset('spam', 'eggs', 20)
1
>>> client.hgetall('spam')
{'eggs': '20'}

pipeline

魅力の一つであるパイプラインを紹介します。
これは一連の処理をトランザクションで行うことが出来ます。

>>> pipe = client.pipeline()
>>> pipe.set('dj', 30).set('voluntas', 20).set('kuenishi', 'spam').mget(['voluntas', 'kuenishi'])
<redis.client.Pipeline object at 0x102535680>
>>> pipe.execute()
[True, True, True, ['20', 'spam']]

これから

redis 2.0 になってオンメモリ以上にデータが持てる仮想メモリ機能がつき、ますます幅が広がりました。
さらに、これから Redis Cluster が登場してきます。

2010 年内には 2.2 がリリースされるようです。

また個人としては UDP 対応や SmallKeys 対応が楽しみです。

今後もブログでちょこちょこっと公開していければいいなと思っています。