Rails × Redis でスレッドセーフなアクセス数ランキング実装
概要
メディアサイトで記事ページへアクセス数ランキングを実装しました。
- Rails 5.1
- Redis (AWS ElastiCache 3.2.10)
その際にマルチスレッド環境を考慮してスレッドセーフな実装を心がけました。
スレッドセーフとは
スレッドセーフとは複数のスレッドが同時並行的に実行しても問題が発生しないことを意味します。
スレッドセーフでない場合は、あるスレッドで変更した共有データが、他のスレッドによって上書きされてしまう可能性があります。
Web サーバーやデータベースなどのサーバー用ソフトウェアは、マルチスレッド(マルチプロセス)で動作しているので、サーバー向けアプリケーションを開発するときは、マルチスレッドで動作するように実装することが望ましいです。
参照
Java の ThreadLocal とスレッドセーフについて
仕様
メディアサイトで記事詳細ページへアクセスした際に
その記事 ID に対して閲覧数を +1 インクリメントします。
そして、
その閲覧数 TOP 10 のランキングを表示する、
というものです。
その際の Rails, Redis の設定についてまとめました。
実装方法検討
config/initializers/redis.rb((host, port は secrets.yml なり ENV で設定してください)) で Redis の初期設定の実装方法を検討しました。
global 変数として設定
1 | require 'redis' |
上記の場合、
グローバルで Redis クライアントを持っており
マルチスレッド環境では、複数のスレッドが上書きされる可能性があります。
Thread.current
1 | require 'redis' |
現在実行中のスレッドを取得しスレッド毎のデータを担保します。
が、以下 2 点の問題があります。
- 他人が上書いてしまう
- 構造化されていない
ActiveSupport::PerThreadRegistry
1 | require 'redis' |
redis をスレッドローカル変数として定義し、そのアクセスをカプセル化し上書きされるのを防止しています。
ですが、
Rails 5.2 で deprecated となっておりました (T へ T)
thread_mattr_accessor
以下を見てみると thread_mattr_accessor
の挙動が Fix していました。
Fix thread_mattr_accessor
share variable superclass with subclass
thread_mattr_accessor
を利用して書き換えます。
- config/initializers/redis.rb
1 | require 'redis' |
アクセス数インクリメント
rescue
設定は Redis に接続できなくなった場合でもサイト自体が落ちることはなく、ランキングだけが表示されなくなる様にする為に設定しました。
1 | def increment_access_count(id) |
アクセスランキング取得
Redis の zrevrangebyscore によりスコアの大きい順に 10 個、ID を取得します。
もし取得できなかった場合、 []
を返します。decorate
で体良く整形して View に渡します。 ((decorate のコードは省略してます))
1 | def access_ranking |
以上です。
Rails × Redis でスレッドセーフなアクセス数ランキング実装
https://kenzo0107.github.io/2018/06/05/2018-06-06-rails-redis-threadsafe/