Redisとデータ構造によるメモリ消費量の違い


今回はRedisというKVSのちょっとした紹介と、利用しているうえでちょっと気になったことがあったので、簡単ですがまとめました。
最初に書いておきますが、話があんまりまとまってません。

RedisとはNoSQLと呼ばれるKVS(Key Value Store)のひとつです。
他のKVSと異なる特長はある程度の永続化が可能ということと、多くのデータ構造を使用できるという点です。
多くのオンメモリのKVSの場合電源断などでデータが飛んでしまいますが、Redisは一定のタイミング(設定で随時書き込みにもできます)でデータをディスクにバックアップするためそのような心配は少ないです。
また、値として配列やハッシュのような構造を格納できるとともに、配列同士の集合演算も行うことができます。

Redisですが、最近仕事で触れる機会があったり、ちょっとしたことにでも利用したりしています。
ちょっとしたアプリのプロトタイプを作る際にRDBMSのスキーマって結構変わったりすると思います。(私の最初の設計が悪いのですが…)
テーブルのスキーマを途中で変えたりするのは意外と面倒だったりします。
そんなときに、データの格納場所としてRDBMSの代わりにRedisを使用し、ある程度データの格納方法が固まったところで必要に応じてRDBMSに移行するということで表側をサクサクと作ることができます。
(うまいこといったらRDBMS使わないでそのままRedisを使えば良いわけですし)

さて、今回はそんなRedisでキーを大量に追加したところ思った以上にメモリの容量を食ったので、ちょっとした測定をしてみました。

たとえばユーザIDからユーザ名を引くのをRedisを使ってやりたいとします。
1つ目の解決策はまずキーにユーザID、値にユーザ名を入れるという風にしました。
違うアプローチとしてハッシュ型を使用して、usernameというキーでハッシュキーとしてユーザID、値としてユーザ名を入れるようにします。
この際の2つの使用メモリ量を比較してみようと思います。

比較を行うために下のようなPythonスクリプトを書きました。

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from redis import Redis
from time import time

def printInfo(redis_con):
    info = redis_con.info()
    print "---------------------"
    print 'use_memory: %s' % info['used_memory_human']
    print 'use_key: %s' % info['db0']['keys']

if __name__ == '__main__':
    redis_con = Redis()
    printInfo(redis_con)
    start_time = 0;

    # Key Value
    start_time = time()
    for i in range(1000000):
        key = 'username:%s' % i
        redis_con.set(key, 'name')
    printInfo(redis_con)
    print time() - start_time
    for i in range(1000000):
        key = 'username:%s' % i
        redis_con.delete(key)

    # Hash
    key = 'user';
    start_time = time()
    for i in range(1000000):
        redis_con.hset(key, i, 'name')
    printInfo(redis_con)
    print time() - start_time
    redis_con.delete(key)

上のコードは、100万人のユーザを想定して、最初に単純なキーバリュー方式、次にハッシュ型を利用して格納しています。
データ自体は、ユーザIDおよびユーザ名(今回はnameで統一)から、1ユーザあたり10バイトだとして、約10MB程度です。
それぞれのデータを格納した際のメモリ使用容量の増加傾向をRedisのinfoコマンドを使用してみてみます。

下が実行した結果です。
Redisのバージョンは2.0.4です。

(dev)[hiroki@minori]~/work/redis% python key_perform.py
---------------------
use_memory: 54.15M
use_key: 1
---------------------
use_memory: 153.70M
use_key: 1000001
69.4635179043
---------------------
use_memory: 115.33M
use_key: 2
71.3940289021

上記の結果から見るに、単純なキーバリューの手法であるとだいたい100MB弱メモリを消費していることがわかります。
一方、ハッシュ型の場合、約60MB弱のメモリ消費量でした。

うーむ…10MB程度のデータで少なくとも60MB以上消費するのは若干食い過ぎではないだろうか。
あと、実行後にデータを消してもinfoコマンドの消費メモリが減らないのは何かキャッシュ的なものが残ってるのだろうか。
(Redisサーバーを再起動すると一気に減るので、GC的なものが何かのタイミングで動くのかもしれない)

落ちがないのですが、ひとまず今回はキーを増やし過ぎたら思った以上にメモリを食ったという体験から、ちょっとした測定だけをしてみました。
なぜ?というところまでは追ってないです、すいません。
こういうときはハッシュ型を使った方がまだましということですかね。

今回残った課題はまた近いうちに調べることにします。

参考サイト

あわせて読みたい

2件のフィードバック

  1. fkei より:

    メモリの開放がされないのは、mmapを利用して一度領域確保してそのままだからだと思うよ。

  2. hiroki.kana より:

    ありがとうございます。

    used_memoryで表示される容量がイマイチ、ピンとこない動きをするんですよね。
    起動時のused_memoryは700KBぐらいで、上のスクリプトを動かした1回目終了後には数十MBになって、2回目以降は増えるわけでもなく、その数十MBのままなんですよね。
    なので、2回目以降は確保した分が解放されたりされなかったりのようにはたからだと見えるような気がするんですよね。

    そもそもソース嫁って話だと思うので、いずれ真面目にソース読んでみます。