前回は、Redis入門と題してRedisの基礎についてまとめました。 このRedisのWebアプリケーションにおける用途のひとつとして、コンテンツの閲覧数に応じたランキングシステムの実装があります。

この記事では、記事の閲覧数に応じたリアルタイムランキングの実装について、Ruby on Railsアプリケーションを例に紹介します。

はじめに

この記事で紹介する内容の開発環境はDockerで構築されていますが、これらは次の2つの記事に詳しく説明しています。 Dockerを用いればRuby on RailsやRedisといった環境を手軽に構築できるので、まだの方はぜひご覧ください。

要件

今回例として紹介するアプリケーションの要件として、次の3つの要件を満たす記事モデルを実装していきます。

  1. 記事を閲覧したらPVを1増やすこと
  2. 記事それぞれのPV数を取得できること
  3. PV数の多い記事から上位n件を取得できること

なお、今回はモデルのふるまいだけを実装します。 コントローラ層によるセッション管理などはランキングロジックの本質ではないのでここでは省略します。

また、データベースには、次のようなSorted set型のデータが格納されることを想定します。 ここでいうvalueは記事ID、scoreは閲覧数です。

key value score
views 101 11
102 41
103 8
: :

Sorted set型はデータを挿入すると、スコアに応じてソートされる型です。 自動かつリアルタイムでソートされるため、ランキングシステムに適した型といえます。

実装方法

それでは、上記の要件を満たすためのモデルを実装していきます。 次のとおり、2つのインスタンスメソッドと1つのクラスメソッドを実装します。

メソッド 処理
#increment_view 記事のPVを1増やす
#views 記事のPV数を取得する
.ranking PV数の多い記事から上位num件を取得する

それぞれの実装を含めたPostモデルは次のようになります。 ここでは、RubyのRedisクライアントをラップしてActiveRecordで使いやすくしたredis-objectsを用いています。

class Post < ApplicationRecord
  include Redis::Objects

  sorted_set :views, global: true

  def increment_view
    self.class.views.increment(id)
  end

  def views
    self.class.views[id].to_i
  end

  class << self
    def ranking(num = 5)
      ids = views.revrange(0, (num - 1))
      ids.map { |id| find(id) }
    end
  end
end

ここで、Post.viewsが閲覧数を表すオブジェクトとなります。 sorted_setglobal: trueとすると、インスタンスごとではなくモデル全体に対するデータベースにすることができます。

動作例

それでは、上記のコードの動作をコンソールで見てみます。 サンプルデータとして、閲覧数が1〜100PVの記事を100件生成し、閲覧数の上位3件とそれらの閲覧数を見てみます。

100.times do
  post = Post.create
  Post.views[post.id] = rand(1..100)
end

Post.ranking(3)
#=> [#<Post:0x0000000386f2d8 id: 53, created_at: ...>,
     #<Post:0x0000000386ce98 id: 89, created_at: ...>,
     #<Post:0x0000000385eca8 id: 40, created_at: ...>]

Post.ranking(3).map { |post| post.views }
#=> [100, 98, 97]

日次/週次ランキングの考え方

上記の内容はPV数の合計によるランキングですが、日次や週次といった他の方法によるランキングの実装も需要があると思うので、考え方の例を書いてみます。

次のような考え方で実装すれば、日次/週次ランキングを実現できます。

  1. ユーザがアクセスしたら日次PVを1増やす
  2. 日付が変わったら過去7日間の日次PVを合計して週次PVとする
  3. 日付が変わったら合計PVに前日の日次PVを加えて新たな合計PVとする
  4. 永続性の担保のためRDBに週次/合計PVを保存する
  5. 日次PVの保存期間は10日間とする

2-4はスケジューラで実行します。 また、このとき冪等性の担保のため、最終計算日をRDBに保存しておくとよいです(例:ranking_calculated_on)。

いろんな基準のランキングシステムが考えられますが、基本的にはこの記事の内容を応用すればよいと思います。

参考記事

おわりに

Redisは揮発性メモリに保存することになるので、設計の際はそのデータが最悪失われることも想定し、格納するデータを慎重に検討したり、リストアの手順を用意した方がよいでしょう。 また、データ量が一定以内に収束するような設計も重要になってきます。

こういった注意すべき点はありますが、Redisはデータ型をサポートしているのでアプリケーションの開発にとても便利です。 ぜひ参考にしてみてください。