Redis四方山話


本日は、Redisで運用している上で起こった事や気をつけている事を、取り留めなく列挙したいと思います。1

Redisについて詳しい説明はしませんが、ごく簡単に説明すると、オンメモリのKVSです。類似のものにmemcachedがありますが、memcachedと比べた優位点としては、自動で並べ替えられるリスト(ランキングで大活躍中)や、永続化機構(サーバーが死んでもバックアップから復帰できる)が挙げられるかと思います。

シングルスレッド

Redisはシングルスレッドなので、強い1CPUが有効です。

数値はdouble

数値周りはintでやり取りするので、何となくint型と思ってしまいますが、double型です。2 ですので、大きな値を代入するとブレます(17桁くらい)。

RDBへの保存中は他のコマンドや新規接続を受け付けない

Redisの永続化の方法はいくつかありますが、そのうちRDBについて、RDBファイルへの保存中、Redisは無応答になります

マシンのスペック次第ではありますが、弊社が利用しているサーバーでは、数GBクラスのデータをRDBへ保存すると、数十秒かかります。その期間中にRedisのデータ操作や参照を行おうとすると、その作業が終わるまでは無応答になります。新規の接続を確立しようとしても、設定次第でタイムアウトし得ます。

saveを無効にして、アクセスが少ない深夜にバッチでRDBに保存する事も考えられます。

ランキングで同順位を処理できない

Redisにはランキング利用に向いているSorted setsというデータ型がありますが、その特徴として、同じポイントのユーザーが同順位になりません。対象のコマンドは以下のとおりです。

  • ZRANK
  • ZREVRANK
  • ZRANGE
  • ZREVRANGE

具体的には以下のようになります( Ruby からの実行例)

# 7500位のユーザーを取得
> user_ids = Redis.current.zrevrange('hogehoge', 7500, 7500)
=> ["aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"]
> user_ids.each do |user_id|
*   pp Redis.current.zscore('hogehoge', user_id)
* end
9256

# 7501位のユーザーを取得
> user_ids = Redis.current.zrevrange('hogehoge', 7501, 7501)
=> ["aaaaaaaa-bbbb-cccc-dddd-ffffffffffff"]
> user_ids.each do |user_id|
*   pp Redis.current.zscore('hogehoge', user_id)
* end
9256# 7500位のユーザーと同じスコア・・・

それに対応するためには、上記4コマンド以外を活用します。例えば、特定のユーザーの現在のランクを取得したい場合、以下のようになります。

# 対象ユーザーのスコアを取得
> score = Redis.current.zscore('hogehoge', "aaaaaaaa-bbbb-cccc-dddd-ffffffffffff").to_i
=> 9
# そのスコアより大きいユーザーの数をカウントし、それに1を足すと対象ユーザーのランクになる
> Redis.current.zcount('hogehoge', "(#{score}", "+inf") + 1
=> 182

それに対応しようとしたパッチもあるようです。

データが溢れると新規データ保存できなくなる

maxmemoryで設定されている容量をオーバーすると、新規データが作れなくなります。3 Railsの場合はエラー扱いとなります。これへの対策として、可能な限りデータの有効期限を設定する事、可能な限り小さなデータにする事を心がけています。

設定値の倍以上のメモリーが必要

RedisはRDBで保存する際に、自身の丸ごとコピーを作ります。そのため、メモリーはmaxmemoryの設定値の倍以上を確保しておく必要があります。

色々あるけどやっぱり便利

色々ありますが、やはり速度が出ますし、自動で並べ替えができるのはありがたいです。今後ともうまく付き合っていきたいものです。


  1. バージョンは2.8.19です。 

  2. IEEE 754 floating point number 

  3. ちなみにmemcachedでは、溢れるとアクセスされた時間が古いデータから消されていきます(LRU)