Blog ブログ

redisを使ったパフォーマンスチューニング

皆様、こんにちわ。
CCSでプログラマをやっている武藤と申します。

今回は『redisを使ったパフォーマンスチューニング』について書かせて頂きたいと思います。

◆『redis』

今更redis?と思われてもおかしくないくらい、言わずと知れた超有名KVSです。
KVS(Key-Value Store)はその名の通り、保存したいデータに対して一意のキーを指定し、ペアとして保存する方式であり、MySQL等のRDB(Relational Database)と比較して非常に高速で動作します。(この記事を見て頂いている方には釈迦に説法ではありますね。)
KVSにも色々種類があり、redis以外にもmemcachedなどの有名なソフトウェアがあります。

◆なぜredisを使用するのか

まず単純に速いです。NoSQLと呼ばれるツール群全般に言えることですが、単純にRDBのディスクI/Oと比べるとかなり高速です。※redisは全てのデータセットをメモリに読み込んでいます。
ただ、単にKVSとして使用するだけであれば、前述のmemcachedなど、選択肢は豊富にあると思います。
redisの特徴として、豊富なデータ構造を持つということが挙げられます。
単純な文字列型の他、数値型、連結リスト、ハッシュ、ソート済みセットと様々です。
これらを上手く利用することがパフォーマンス向上に繋がるわけですね。
他にもデータ永続化やレプリケーションなどがありますが、今回は割愛させて頂きます。

下記では、具体的な手法をいくつか述べていきたいと思います。

◆リアルタイムランキング

ソート済みセットを利用することで、簡単にリアルタイムランキングを実装することができます。
実は以前に弊社のエンジニアがこんな記事を書いていたりします。

Redisでランキング実装してみませんか

従来(割と今更な話なので適切な言葉ではないかもしれませんが)ランキングを実装するためには、バッチ処理である時点での全体のスコアを集計し、ランク付けすることが必要でしたが、それが必要なくなります。
集計処理はサーバ側でかなり負荷がかかる処理ですので、これが無くなるのはかなりおいしいと思います。
とはいえ、数万程度のデータ更新であれば十分捌けますが、数十万、数百万規模のデータ更新が入る場合はさすがのredisも根を上げてしまうようなので、データ件数には注意して運用しましょう。

◆DBのシャーディング

負荷分散のテクニックとして、シャーディングという手法があります。
DBサーバの負荷が1台に集中しないよう、数台にデータを分ける手法です。
データ分散によるパフォーマンスの向上や、スケールアップではなくスケールアウトを狙ったものですが、課題の1つとして、シャーディングされた同一テーブルにどうやって一意のIDを割り当てるか、という問題があります。


例えば、ユーザが所持しているアイテムを管理するテーブルがあるとします。
このテーブルのレコードでもし識別IDが被ってしまった場合、処理対象の特定ができなくなり、あるユーザがアイテムを使用した場合、他のユーザの所持数も減ってしまう、という状況が起こり得ます。

弊社では、このIDの生成にredisを使用しています。
redisには「INCR」コマンドという、値をインクリメントして取得するというコマンドが存在します。
この「INCR」コマンドはアトミックな処理となっており、複数プロセスから同時実行されたとしても一貫性が保たれるようになっています。
これをRDBで行おうとした場合、

①ID管理のテーブルからレコード取得
②値をインクリメント
③増分のデータを保存

と処理した上で、①〜③の間でトランザクションを貼らなければなりません。嫌ですね。
極端な例ではありますが、これが簡単に実装できるのがredisなのです。

◆オススメ編成

例えばRPG系のスマホゲームでパーティを組む場合、「オススメ編成」や「○○優先で編成」という項目がよくあると思います。
これも素直に実装してしまうと、

①所持している全てのキャラクターを取得(するためにストレージアクセス
②取得した全てのキャラクターを比較して目的にあったレコードを選択
③編成

というプロセスを踏まなければなりません。
これが小規模のゲームであればいいのですが、ユーザ数が数十万、数百万となった場合、加えて、これに装備などの概念が出てきてしまった場合は、全てのキャラクターとそれにセットされた全ての装備を取得し、総合してどれが優先か、という判断を下さなければなりません。ゾッとしますね。

ここでいうパーティ編成を、redisでハッシュとして持っておくというやり方があります。
例えばキャラクターを手に入れたとき、あるアイテムを装備させたときに、そのハッシュを取得し、更新するのです。

例:
あるユーザのキャラクター関連データが更新されたときに、redisのハッシュを組み替える実装を入れた例です。

[a]初期状態
[b]キャラクターを手に入れたとき
[c]キャラクターを強化したとき
[d]キャラクターの装備変更を行ったとき

に、それぞれ最も’power’が低い’member’と、今回の処理対象の’member’の’power’を比較し、更新を行っています。

[a], 初期状態(メンバーが3人)のオススメの状態
‘osusume_party’:{
  ‘member_1’:{‘id’:1, ‘power’:100},
  ‘member_2’:{‘id’:2, ‘power’:200},
  ‘member_3’:{‘id’:3, ‘power’:300},
}

[b], ‘id’:4, ‘power’:400のキャラクターを手に入れたので、オススメを再編成
‘osusume_party’:{
  ‘member_1’:{‘id’:4, ‘power’:400},
  ‘member_2’:{‘id’:2, ‘power’:200},
  ‘member_3’:{‘id’:3, ‘power’:300},
}

[c], ‘id’:1のキャラクターを強化して’power’:350になったので、オススメを再編成
‘osusume_party’:{
  ‘member_1’:{‘id’:4, ‘power’:400},
  ‘member_2’:{‘id’:1, ‘power’:350},
  ‘member_3’:{‘id’:3, ‘power’:300},
}

[d], ‘id’:2のキャラクターにアイテムを装備して’power’:330になったので、オススメを再編成
‘osusume_party’:{
  ‘member_1’:{‘id’:4, ‘power’:400},
  ‘member_2’:{‘id’:1, ‘power’:350},
  ‘member_3’:{‘id’:2, ‘power’:330},
}

というような形です。
そして、このハッシュを’power’優先編成実行時に取得し、実際のパーティとするわけです。
注意しなければならないのは、これを実装する場合は、上記の[b]、[c]、[d]の過程で別途オーバーヘッドが発生するということです。
①、②、③の処理で十分パフォーマンスが確保できるのであれば、余計な場合もありますので、吟味する必要があることは忘れないで下さい。

◆というわけで

・『redisを使ったパフォーマンスチューニング』
について簡単に書かせて頂きました。
今回は割愛しましたが、redisはパフォーマンスに優れているだけではなく、便利な機能が盛りだくさんなので、みんなでredisをフル活用してサクサク動くゲームを作っていきましょう。
※こんな使い方があるよ、うちではこう使ってるよ、などあればこっそり教えて頂けると幸いです。
また、クラウドクリエイティブスタジオでは絶賛エンジニア募集中です。
興味のある方は、是非、以下のリンクをご覧下さい。
採用情報

今回は以上となります。それでは皆様、快適なredisライフを!