Building a Thread-Safe Access Count Ranking with Rails × Redis
Overview
I implemented an access count ranking for article pages on a media site.
- Rails 5.1
- Redis (AWS ElastiCache 3.2.10)
In doing so, I took care to write a thread-safe implementation that accounts for a multi-threaded environment.
What is Thread Safety?
Thread safety means that no problems occur even when multiple threads run concurrently.
When code is not thread-safe, shared data modified by one thread may be overwritten by another thread.
Server-side software such as web servers and databases runs in a multi-threaded (multi-process) manner, so when developing server-side applications it is desirable to implement them to work correctly under multiple threads.
References
About Java’s ThreadLocal and thread safety
Specification
When a user accesses an article detail page on the media site,
the view count for that article ID is incremented by +1.
And then,
it displays a ranking of the top 10 most-viewed articles.
I have summarized the Rails and Redis configuration used to achieve this.
Considering Implementation Approaches
In config/initializers/redis.rb((set host and port via secrets.yml or ENV)), I considered how to implement the initial Redis setup.
Defining it as a global variable
1 | require 'redis' |
In this case,
the Redis client is held globally,
and in a multi-threaded environment multiple threads may overwrite it.
Thread.current
1 | require 'redis' |
This retrieves the currently running thread and guarantees per-thread data.
However, there are the following two problems.
- It can be overwritten by others
- It is not structured
ActiveSupport::PerThreadRegistry
1 | require 'redis' |
Here redis is defined as a thread-local variable, encapsulating its access and preventing it from being overwritten.
However,
this became deprecated in Rails 5.2 (T_T)
thread_mattr_accessor
Looking at the following, the behavior of thread_mattr_accessor had been fixed.
Fix thread_mattr_accessor share variable superclass with subclass
Let’s rewrite it using thread_mattr_accessor.
- config/initializers/redis.rb
1 | require 'redis' |
Incrementing the Access Count
The rescue clause is set up so that even if the connection to Redis is lost, the site itself does not go down—only the ranking stops being displayed.
1 | def increment_access_count(id) |
Retrieving the Access Ranking
Using Redis’s zrevrangebyscore, we fetch 10 IDs in descending order of score.
If nothing can be retrieved, it returns [].
We tidy things up nicely with decorate and pass it to the View. ((the decorate code is omitted))
1 | def access_ranking |
That’s all.
Building a Thread-Safe Access Count Ranking with Rails × Redis
https://kenzo0107.github.io/en/2018/06/06/rails-redis-threadsafe/
