Help us understand the problem. What is going on with this article?

Rails + Redis + AWSでPV数を保存

More than 5 years have passed since last update.

AWS上で動くRailsアプリのページビューを保存する方法について書いていきます。
今回は基本的なことのみ書きますが、例えばブログのデイリーPVランキング、個別ページのPV数の推移グラフの作成などに応用出来ます。

RedisをAWSに追加

AWSには、 ElastiCache というRedisやMemcachedなどのNoSQL専用のサービスがあります。
新規Redisインスタンスの作成手順は簡単なので、他のサイトの記事を参考にしてRedisを追加してみてください。

僕は下のサイトを参考にしました
Using AWS ElastiCache for Redis With AWS OpsWorks

RedisをAWSに追加して、インスタンスの詳細画面に移動してください。

elasticache-instance-details

RailsでRedisを使う際に、画面のPortとEndpointを使用するので、Railsの環境変数に追加しておきましょう。

config/environments/production.rb
ENV["REDIS"] = "xxxx.xxxx.chache.amazonaws.com:6379" # Endpoint + Port

AWSで追加したRedisは、AWSのRailsインスタンスからしか接続できません。従って、ローカルからはElastiCacheのRedisに接続することが出来ないので、もしAWSではなくローカルでRedisを使いたい場合は、以下のようにRedisをインストール、設定してください。

1. Redisのインストール (Mac)

$ brew install redis
$ redis-server

2. Railsの環境変数に追加

config/environments/development.rb
ENV["REDIS"] = "localhost:6379"

RailsでRedisを使うための設定

RailsでRedisを扱うためにgemを追加します

Gemfile
    gem 'redis'
$ bundle install

initializerフォルダに、Redisの初期設定を追加します。

config/initializers/redis.rb
require 'redis'

uri = URI.parse(ENV["REDIS"])
REDIS = Redis.new(host: uri.host, port: uri.port)

これで、REDISという変数をRailsから呼び出すことによって、Redisを使えるようになりました。

RedisにPV数を保存

例えば、CMSアプリケーション(ブログなど)で使う場合を考えます。
Postモデルがあって、PostsControllerからページの操作をするとします。

PostControllerのshowメソッドで、個別ページを表示するなら、以下のコードを追加します。

controllers/posts_controller.rb
def show
    @post = Post.find(params[:id])
    ...

    REDIS.incr "posts/daily/#{Date.today.to_s}/#{@post.id}"
end

REDIS.incrは、数値に1を足して保存するメソッドです。
例えば、2014年4月1日に、id=100の記事が初めて読まれたとすると、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100
に「1 (= 0 + 1)」が保存されます。

もう一度読まれた場合は、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100
に「2 (= 1 + 1)」が保存されます。

さらにもう一度読まれた場合には、

Redisの管理するハッシュのキー: posts/daily/2014-04-01/100
に「3 (= 2 + 1)」が保存されます。

このように、ページビュー毎にID別のPV数がRedisに保存されます。

RedisからPV数を取得

REDIS.get "posts/daily/#{Date.today.to_s}/#{@post.id}"
# => 今日のPV数

REDIS.get "posts/daily/#{Date.yesterday.to_s}/#{@post.id}"
# => 昨日のPV数

例えば、全ての記事に関してPV数を取得すれば、毎日の人気記事ランキングを作成出来ます。

Ex.)

@posts = Post.all
@daily_pageviews = Hash.new
today = Date.today.to_s

# 個別記事のPV数を取り出す
@posts.each do |post|
    @daily_pageviews[@post.id] = REDIS.get "posts/daily/#{today}/#{post.id}"
end

# PV数のソーティング
@daily_pageviews.sort_by{|k, v| v}

#上位10個の記事を返す
@top10_pages = @daily_pageviews[0..10]

(追記) より良い方法

Redisにはソート済みセットという型が用意されています。もし月間PVランキングみたいなものを実装したいのであれば、この型を使うと自動的にランキングが出力されるので便利です。

ソート済みセットの関数「zincrby」は、「キー・数値・メンバー」を引数とし、あるメンバーにキーが存在すれば数値分だけ増やし、キーが存在しなければ数値をセットします。

例えば、「2015/1/1の記事」というメンバーに「id=10の記事」というキーがあって、「PV数が100件」といった数値があると考えると、PV数のランキングは以下のように実装できます。

def show()
  @post = Post.find(params[:id])
  ...
  # ex.) REDIS.zincrby "posts/daily/2015-01-01", "1", "10"
  #      2015年1月1日にid=10の記事の総PV数を1増やす
  REDIS.zincrby "posts/daily/#{Date.today.to_s}", 1, "#{@post.id}"
end

zrevrange関数は、メンバーの降順にソートされた数値の中から、指定された範囲の数値を持つキーを取得します。昇順の場合はzrange関数を使います。
このzrevrangeを使うと、PV数のランキングデータを取得するのも2行で済みます。

# PV数1位から20位までの記事を取得
ids = REDIS.zrevrange "posts/dayly/#{Date.today.to_s}", 0, 19
@posts = Post.where(id: ids)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away