記事のPV数ランキングを実装したかったので、Redisを利用して実装してみたいと思います。 ちなみに、Redisを使うに至った経緯は、
- MySQLにPVテーブルを持たせると負荷上がりそうだから避けたい。
- とはいっても簡単にランキング機能を実装したい。
- じゃあ、Google AnarithicsのAPIかRedisが良さそう。
- Redisで作ったことないしちょっと触ってみるか!
といった感じです。
オススメの実装方法等ランキング機能実装の知見がある方はコメントにてご教示頂けますと幸いです。
というわけで、実装します。今回はlocal環境までしかやっていません。AWSのElasticCacheでproductionは用意しようと思っています。
準備
こちらの記事が今回方針を立てる上で役立ちました。
$ brew install redis $ redis-server # redisの起動
でredisを用意。
コマンドに関してはこちら記事を参考に。
次に初期設定を書きます。環境変数の設定をして下さい。 僕は.env
に書きましたが、ご自由に環境変数を設定して下さい。
REDIS=localhost:6379
gemファイルに
gem 'redis'
でインストールして
以下のconfig/initializer/redis.rb
を追加します。
require 'redis' uri = URI.parse(ENV["REDIS"]) REDIS = Redis.new(host: uri.host, port: uri.port)
これで準備は完了です。
PVを登録する
ランキングの仕組みは以下の記事を参考にしました。
記事の個別ページにアクセスしたときにそのアクセスデータを保存します。
def show(id) @article = Article.find(id) REDIS.zincrby "articles/daily/#{Date.today.to_s}", 1, @article.id end
zincrby
というメソッドはキーに対して指定されたメンバーの値のインクリメントが出来ます。
ここで「?」となるかもなので、Redisとは何ぞやって話なのですけど、KVS(キーバリューストア)というDBの仲間です。キーに対してバリューを持つという単純な構造を持ったDBです。
rubyっぽく表すとkey: value
みたいなのがKVSの基本構造で、今回使うのはソート済みセット型と呼ばれるkey: { member: score }
のような構造だと考えると分かりやすいかと思います。以下のように値が入ります。
key | member | score |
---|---|---|
2016-5-2 | 123 | 2 |
2016-5-2 | 124 | 6 |
2016-5-2 | 125 | 1 |
2016-5-2 | 126 | 4 |
Redisに関してはこちらも参考になりました。
話を戻して今回は以下のような書き方をしました。
REDIS.zincrby "articles/daily/#{Date.today.to_s}", 1, @article.id
これは key "articles/daily/#{Date.today.to_s}"
に対してvalueは member @article.id
の scoreに 1
プラスするという意味を表します。
つまり、アクセスするごとに1足してPVをカウントするのです。ちなみに該当するキー・メンバーがいないときは勝手に作ってくれるので便利です。
ランキングを表示する
以下のような感じにPVを取り出してみました。昨日のPVランキングです。
def set_ranking_data ids = REDIS.zrevrangebyscore "articles/daily/#{Date.yesterday.to_s}", "+inf", 0, limit: [0, 5] @ranking_articles = ids.map{ |id| Article.find(id) } if @ranking_articles.count < 5 adding_articles = Article.order(published_at: :DESC, updated_at: :DESC).where.not(id: ids).limit(5 - @ranking_articles.count) @ranking_articles.concat(adding_articles) end end
ids = REDIS.zrevrangebyscore "articles/daily/#{Date.yesterday.to_s}", "+inf", 0, limit: [0, 5]
こちらのメソッドなのですが以下のURLを参照して下さい。
Method: Redis#zrevrangebyscore
説明しますと、zrevrangebyscore
が指定したキーのバリューをscore順に並び替えてmemberをscoreの高い順に返してくれる便利なメソッドです。"+inf"は値の上限はなしを意味し、0は値の下限が0であることを示します。値の制限をつけるのはパフォーマンスで必要になることがあるかららしいのですが、今回の場合はこのような設定で良いと思いました。それでlimitは何件取得するかです。今回は上位5件を取り出します。
ここでArticleのインスタンスを取り出しています。
@ranking_articles = ids.map{ |id| Article.find(id) }
間違っても以下のようにしてはだめです。これだとランキング順ではなくid順で取得されます。
@ranking_articles = Article.where(id: ids)
あとは、idsが5件未満の対策をしておきました。必ず5件取得したかったので。
if @ranking_articles.count < 5 adding_articles = Article.order(published_at: :DESC, updated_at: :DESC).where.not(id: ids).limit(5 - @ranking_articles.count) @ranking_articles.concat(adding_articles) end end
以上でRedisによるランキング機能の説明は終わります。
週ごとのランキングや月ごとのランキングも出来そうなので必要になったらやってみようと思います。