GAミント至上主義

Web Monomaniacal Developer.

ユーザーの閲覧履歴をRedisのSorted Setを使って保存&表示する

ユーザーの閲覧履歴をDB(MySQL)からBigQueryに移すにあたり、既存のDBを使っている「最近見たお仕事」を表示する機能も変更が必要になりました。

「最近見たお仕事」は文言は異なるものの、よくECなどでも「最近チェックした商品」、不動産系では「最近見た物件」などと表示されてるやつです。

BigQueryに入っている履歴を使うのも考えましたが大きく以下の3点から別の実装を検討しました。

  • スキャン料金を抑えたい
  • クエリに時間がかかる
  • タイムラグがあるためさっき見たページが表示されない可能性がある

セッションも考えたけど、同じくDBに入ってしまうので、負荷がかかるのでいまいち。

そこで考えたのがRedis。ずっと残しておく必要も無いデータなのでちょうど良さそう。

ハッシュ型でもできそうですが、いろいろ使える型を見ているとSorted Setが今回の用途にぴったりでした。

RedisのSorted Setとは

一つのキーに対してスコアを持った複数の値(メンバーと呼ばれる)を保存でき、常にスコア順に入っているのでスコア順で高速に取得ができます。
またSetなので、重複は入りません。最近みたページには丁度いい

その特徴からよくランキングに使われています。
qiita.com

今回はスコアにタイムスタンプ値を使うことで「最近見たお仕事」を実装しました。

保存する処理

ZADDコマンドを使います。

使用する言語、ライブラリなどにより異なりますが、生のRedisコマンドで書くとこんな感じ。

redis> ZADD recenrtyview:{ユーザーID}  タイムスタンプ値 "{ページID}"

適当なprefix (上記だとrecenrtyview)とユーザーIDをキーとして、事前に出しておいたタイムスタンプ値と、ページのIDを入れておきます。
この処理をページを見たときの処理に入れておけばOK。

有効期限を設定する

今回はさらにその追加した値に有効期限を付けて、ゴミが溜まらないようにしました。

下記を参考に念のため、MULTIを使ってトランザクションもしておきます。
qiita.com

EXPIREは秒で指定するため、今回は30日、 60*60*24*30 = 2592000を使います。

redis> MULTI
redis> ZADD recenrtyview:{ユーザーID}  タイムスタンプ値 "{ページID}"
redis> EXPIRE recenrtyview:{ユーザーID}  2592000
redis> EXEC

もし同じページIDを見た場合はスコアのタイムスタンプ値と、有効期限の両方が更新されます。

取得する処理

今回は新しい順、タイムスタンプ値が大きい順に取得したいので、ZRANGEの逆版、ZREVRANGEを使います。

ZREVRANGE – Redis

redis> ZREVRANGE recenrtyview:{ユーザーID} {start} {stop}

start, stopのindexは0始まりなので、最新10件であれば下記のような感じ。

redis> ZREVRANGE recenrtyview:{ユーザーID} 0 9

これで、保存されたページIDが欲しい順番で返ってくるので、それぞれの情報を取得して、表示すれば完了です。

今回実装したのは下記のサイトのそれぞれのお仕事詳細ページの下の方で使われています。
シニア・中高年の求人・仕事探しなら【シニアジョブ】
f:id:uyamazak:20200526164000p:plain
情報の入れ替わりが激しいのでぼかしといた


ということで、ランキング以外にもSorted Setが使うのがぴったりな用途を見つけたので記事にしました。
RedisのSorted Setを使うことで、高速で、かつ構造もシンプルでバグも少ない実装が出来た気がします。