なぜ私のredisはこんなにも遅かったのか


RedisってKVSやしチョー早いんやろ?

と、そう思っていた時期が私にもありました。
Redisを利用する上で、適切に利用しないと性能が1/10以下に陥ってしまいます。
この記事では、redisの性能を発揮する実装と発揮できない実装で性能測定を行いました。

結論

最初に結論

Redisの読み書きはmget/msetなどの複数のデータを一度に読み書きするコマンドを用いることで本来の性能が発揮できる。
逆に単純にforを用いてget/setなどのコマンドで1件単位でループを回すと大きな性能劣化が発生する。

環境

python3で計測します。

redisはdockerで動かしました。

$ docker run --name redis -d -p 6379:6379 redis:3.2.8
$ redis-cli
127.0.0.1:6379> ping

redis-cliは別途インストールしたけれど、記事内ではこの疎通確認にしか使いません。

計測する

10000件のデータを書き込んで測定してみます。
条件を揃える目的で、データを書き込む前にflushdb()によってredisをきれいな状態にして計測します。
また、pythonのtimeitモジュールで1回の実行速度についてをまとめています。

loopで1件づつ書いてみる

$ cat forloop.py 
import timeit
code = '''
import redis
r = redis.StrictRedis()
r.flushdb()
for i in range(10000):
    r.set(i, i)
'''
print(timeit.timeit(code,number=1))
$ python forloop.py 
5.071391730001778

10000回の書き込みで約5sec。
1回あたり0.5msecですが遅いとまでは、この時点ではあまり思っていなかった…

100件づつ100回

10000件の書き込みするために、「100件づつ100回」の書き込みにまとめてみます。
redisのコマンドで言うと、setがmsetになります。

$ cat chunk.py 
import timeit
code = '''
import redis
r = redis.StrictRedis()
r.flushdb()
for i in range(100):
    kv = {str(j):j for j in range(i*100,(i+1)*100)}
    r.mset(kv)
'''
print(timeit.timeit(code,number=1))
seiket-mba2011:sandbox seiket$ python chunk.py 
0.2815354660015146

10000件書くのに約0.28sec
1件あたり0.028msec
1件づつ書き込みの10倍以上早い。
ココまで違いが出るのか!!

10000件1発で書き込む

せっかくなので、10000件を、一気に書き込む

$ cat all.py 
import timeit
code = '''
import redis
r = redis.StrictRedis()
r.flushdb()
kv = {str(j):j for j in range(10000)}
r.mset(kv)
'''
print(timeit.timeit(code,number=1))
seiket-mba2011:sandbox seiket$ python all.py 
0.22943834099714877

更に高速化してますね。

測定結果とまとめ

redisへの書き込み速度はまとめて書くと圧倒的に速度に違いが出る。
10000件の書き込みについて以下のように違いが出た。

書き込み方法 1万件の書き込み所要時間(sec)
1件ずつ10000回 5.071
100件ずつ100回 0.281
10000件ずつ1回 0.229

上の表を見て分かるように、まとめてredisへの書き込みを行うことによって10倍以上の性能向上(あるいは、1件ずつ書くことによって1/10以下の性能劣化)が発生する。
どちらかと言うと、性能劣化のほうが適切だとは思う。

記事内では触れてないが読み込みでも同様の傾向があるのでまとめて読み込むほうが性能面で圧倒的によい。
特に速度目当てでredisを使う場合に1件ごとアクセスするのは致命的に遅い。

gist

計測に使ったソースコードは記事内と同一ですが以下にもおいてます。
https://gist.github.com/seiketkm/3ca7deb28e8b0579a6e443cc7f8f9f65