Redisでのbulk処理

Redisと仲良くなりたいです!って隣の人に言ったら、「障害に遭遇すると良いよ。それも深刻なやつにね。軽いやつだと軽い関係にしかなれないから」って言われたwyukawaです。こんばんは。

軽い関係じゃなくてもうちょっと踏み込んだ関係になりたいと思ってますが、まずは友達からお願いします > Redisさん

Redisでbulk処理やりたいってことありますよね。listにあるデータを1つづつlpopするんじゃなくてまとめてがつっとlpopしたいなんて状況です。

今回はrubyからアクセスする例ですので下記を使います。
https://github.com/redis/redis-rb

例えば10万件のデータを1つづつlpopするような以下のコードを試したところ僕の環境(Mac 10.7.5, rbenv+ruby 1.9.3p286, redis-rb 3.0.2, Redis 2.6.2)ではlpopに8秒ほどかかりました。コードにあるようにrpush時間は除いています。

require 'redis'

r = Redis.new

queue="nopipeline"
count=100000

1.upto(count) {|i|
  r.rpush(queue,"#{i}")
}

start = Time.now

result = []
1.upto(count) {
  result << r.lpop(queue)
}

puts result.length

puts "#{Time.now-start} seconds"


どうするのか調べてたら
http://redis.io/commands/lpop
のcommentにpythonコードのスニペットがあったのでこれを参考にrubyでも書いて試してみました。

こんな感じです。これを試すと1秒未満です。早いですね。

require 'redis'

r = Redis.new

queue="pipeline"
count=100000

1.upto(count) {|i|
  r.rpush(queue,"#{i}")
}

start = Time.now

size = count
future = nil
r.pipelined do
  r.multi do
    future = r.lrange(queue, 0, size-1)
    r.ltrim(queue, size, -1)
  end
end

result = future.value

puts result.length

puts "#{Time.now-start} seconds"

pipelineというのは言ってみればパラレルで処理するみたいなやつのようです。

本家のドキュメントはこちら
http://redis.io/topics/pipelining

日本語訳はこちら
http://redis.shibu.jp/developer/pipelining.html

multiはatominな処理を実現するやつみたいです。

r.pipelined do
  r.multi do
    future = r.lrange(queue, 0, size-1)
    r.ltrim(queue, size, -1)
  end
end

とあるところのlrangeで0からsize-1分だけ取得します。
ltrimはlistからデータを削除するものです。
size番目から-1つまり最後までのデータを残して残りを削除します。
つまりlrangeで取得した0からsize-1分を削除することにります。

future.valueのところでデータを取得できます。

今回の例だと10万件のデータを1回で取ってくることになります。

phpでpipeline処理する場合は下記が参考になりそうです。
http://gihyo.jp/dev/feature/01/redis/0003

以上です。


2013/06/28 追記

pipelinedとmultiをセットで使うとmultiが効かないようです。redis-cli monitorでチェックしてもmultiが出てきませんでした。試した環境はMac 10.8.4, rbenv+ruby 1.9.3p327, redis-rb 3.0.4, Redis 2.6.10。

代わりに

r.multi do
  future = r.lrange(queue, 0, size-1)
  r.ltrim(queue, size, -1)
end

のようにpipelinedを外して実行すると下記のようにmultiになりました。性能もpipelinedとmultiをセットで使っていたときと比べてそんなに変わりません。

$ redis-cli monitor
...
1372423549.654902 [0 127.0.0.1:50454] "multi"
1372423549.654989 [0 127.0.0.1:50454] "lrange" "pipeline" "0" "99999"
1372423549.718903 [0 127.0.0.1:50454] "ltrim" "pipeline" "100000" "-1"
1372423549.744641 [0 127.0.0.1:50454] "exec"
...

redis clientをマルチプロセスで実行する場合は要注意でしたね。。。