Redis
AWS

巨大なデータを扱う場合のRedisの運用戦略

More than 3 years have passed since last update.

はじめに

Read/Writeともに高速で,様々なデータの持ち方が可能なことでキャッシュDBとして人気のあるRedisですが,何も考えずに実運用システムで使用しているとデータが肥大化してしまい非常に扱いにくくなることがあります.
今回は,データの肥大化とともに顕在化する問題と,データの肥大化に対する戦略についてまとめたいと思います.

データの肥大化時に顕在化する問題

何のキーが入っているか分からなくなる

Redisはオンメモリ型のKVSであるため,データがある程度増えてくるとサーバのメモリ容量を圧迫し始めます.
このような状態で,プロダクション環境に対して keys * などをやってしまうと,一時的にメモリ使用量が跳ね上がり,メモリ使用量を抑えるためにRedisがキーを削除したり,OOM KILLERにRedis Serverごと殺されてしまう可能性があるため,そういったコマンドはうてなくなってしまいます.
つまり,Redisの中に何のキーが入っているか確認できなくなり,謎のデータが増えていくことになります.

気軽にslaveノードを追加できなくなる

データ量が少ない状態においては,slaveノードを追加することはコマンド一発で簡単に行うことができます.
しかし,データ量が多くなると突然非常に神経を使う必要が出てきます.
Redisは,slaveノードを追加するときに,master側で一度DB内のデータをファイルとして保存し,それをslaveに送信して読み込むような流れになっています.このときに,master側でメモリが足りないと,ファイルとして保存する処理がメモリ不足で失敗してしまい,slaveノードの追加ができなくなってしまうことがあります.

データの肥大化に対する戦略

maxmemoryを余裕を持って設定する

redisではredis.conf内でmaxmemory(redisが使用する最大のメモリ量)を設定することができます.設定したメモリを超えた場合には,redis側がキーの削除戦略(allkey-lruなど)に従って自動で削除していきます.
大体maxmemoryの設定は,サーバの使用可能なメモリの半分程度が望ましいです(=必要なメモリの2倍程度のメモリをサーバに積むのがよい).

vm.overcommit_memoryを設定する

ある程度使用メモリが大きくなるとBGSAVEがメモリ不足で失敗したり,最悪の場合OOM KILLERによってRedisのプロセスを殺される可能性があります.対処方法としては,overcommit_memory = 1を設定してスワップ可能にしておきます.

設定には,以下のコマンドを実行してください.

# sysctl vm.overcommit_memory=1

/etc/sysctl.conf に以下の行を追加してください.

vm.overcommit_memory = 1

巨大なデータを持つキーを作らないようにする

Redisは,一般的なKeyValueの形式だけでなく,Hash型などの様々なデータ型でデータを保持することができるため,複雑なデータ構造のデータを持つことができます.
これらのデータ型はメモリ使用効率が良いため,一つのキーに複数のデータを持つことは一般的に行われています.
しかし1つのキーに割り当てるデータがあまりにも大きくなる(1GBを超えるなど)と,そのキーが削除されたときに,masterとslaveの保持するデータの差分が大きくなってしまい,通常のsyncではなくbgsaveが走ってしまい,Redisに負荷がかかってしまったり,bgsaveが失敗しデータのsyncが止まってしまう危険性があります.
こういった場合は,LTRIMなどのコマンドでデータのトリミングをしたり,キーを複数に分けるなどして,一つのキーが巨大なデータを持たないように管理する必要があります.

例えば,ユーザのページごとのアクセス時間を管理した場合を考えます.

user_page_access_time:
    "user_id:{page_id}": last_access_datetime

このような持ち方をしてしまうと,

ユーザ数 x ページ数

のデータが一つのキーに保存されることになり,ユーザとページ数が増えるごとに指数的にデータ量が増えてしまいます.

こういった場合は,以下のようにユーザidごとにキーを分けることで,一つのキーで持つデータサイズを削減することができます.

user_page_access_time:{user_id}:
     "{page_id}": last_access_datetime

キーにExpireを設定する

実運用システムでは,放っておくとどんどんキー数は増加していきます.
そのため,データのライフサイクルを考え,可能な限りExpireを設定するようにします.
Expireを設定したキーはその時間がくると自動的に削除されるため,データ量の増加を防ぐことができます.
既にデータがある場合には,一度キー名を取得してから一括でExpireを付けるようなスクリプトを書いたので参考にしていただけたらと思います.

Redisのキーに一括でexpireを付与する

また,このときにExpireを付けられないキー(=永続的に持っておく必要があるキー)が多く存在する場合は,キャッシュとしてRedisを適切に使用できているか検討する必要があるかと思います.

おわりに

コストもかからない上に非常に高速でかつデータの持ち方も豊富で,扱いやすいオンメモリ型のDBとして人気のあるRedisですが,利便性が高い故にキャッシュDBに枠を超えて大量のデータを持ってしまいがちです.
データのライフサイクルを考えて,必要以上にRedisにデータを持たないように運用することが肝かと思います.

参考

Redis Documentation
Redisを使う時は見積の二倍の容量必要だよね、という話