📜 ⬆️ ⬇️

Using Redis EXPIRE to Track Rails Online Audiences

To whom




The solution to the question of whether a user is online or not is probably as a rule setting a timestamp when a user accesses an application, and if you need to know his (user) current status, check with this timestamp. Which approach to choose is up to you, but the one I propose is simple and does not use a SQL database, instead Redis used and one of its built-in capabilities is the key lifetime ( expire ).


')

Actually implementation


Init


 # config/initializers/redis.rb $redis_onlines = Redis.new 

The above is the simplest approach, but I recommend the following

 # config/initializers/redis.rb $redis_onlines = Redis.new path: "/tmp/redis.sock", db: 15, driver: :hiredis 


Gemfile


 # Gemfile gem 'redis' gem 'hiredis' # optional 

Don't forget to run the bundle

Install online


current_user method
The current_user method is most likely already used by you - this is the method that returns the current user or nil - if the user is not logged in.
 def current_user @current_user ||= User.find_by_id( session[ :user_id ] ) end 


 # app/controllers/application_controller.rb after_filter :set_online #  Rails 4 : # after_action :set_online #     set_online private def set_online if !!current_user #   ,    $redis_onlines.set( current_user.id, nil, ex: 10*60 ) # `ex: 10*60` -     - 10 ,  10    end end 

Online?


  # app/models/user.rb def online? #      -   false,  true $redis_onlines.exists( self.id ) end 


Small bonus - list of online users


 # app/cpntrollers/application_controller.rb def all_who_are_in_touch $redis_onlines.keys # => [ "123", "234", "1", "23" ] #    id   end 


On this and all

Some processing for anonymous



For tracking anonymous visitors (those who are not registered / not entered), the approach is the same, with a small addition.

Installation online
  # app/controllers/application_controller.rb def set_online if !!current_user #       "user:"  id $redis_onlines.set( "user:#{current_user.id}", nil, ex: 10*60 ) else #      "ip:"    id  $redis_onlines.set( "ip:#{request.remote_ip}", nil, ex: 10*60 ) end end 


online?
 # app/models/user.rb def online? $redis_onlines.exists( "user:#{self.id}" ) end 


online user list
 # app/cpntrollers/application_controller.rb #     (   id) def all_signed_in_in_touch ids = [] $redis_onlines.scan_each( match: 'user*' ){|u| ids << u.gsub("user:", "") } ids end #      def all_anonymous_in_touch $redis_onlines.scan_each( match: 'ip*' ).to_a.size end #     def all_who_are_in_touch $redis_onlines.dbsize end 



Well, just a little bit about the size of the database


9000 + 9000
Redis stores data in RAM, so overkill with the size of the database can adversely affect the work of the entire server. For evaluation, an empty database was used (performed by FLUSHALL before this) and this small ruby ​​script . For 9000 online users and 9000 online Anonymus it turned out like this:

  1. empty database: 810.75K
  2. 18,000 records: 3.49M

Similarly for 65000 + 65000

  1. 130,000 entries: 18.66M



#UDP 1


The wrapper in pipelined replaced by the use of the ex: timeout option in the set call. Thanks to printercu for the tip. A small test [ src: ruby ] showed a significant increase in performance.

#UDP 2


A couple of clarifications / recommendations:
  1. Use before_filter instead of after_filter - then the logged-in user will see himself on the list online during the first (in the next 10 minutes) visit. But then the choice depends on your needs / wishes.
  2. If an anonymous audience is being counted and at the same time logged in users - then when a user logs in / out, by and large it would not be bad to clean the user from the opposite list. For example, a user logs on to the site (an entry by ip is in progress), and then becomes authorized (the second entry by id) - as a result, in the near future (conditional 10 minutes) one user will be considered as two. Example implementation in the spoiler below.

clearing the opposite list
 # app/cpntrollers/session_controller.rb #   #  -  create #  - destroy before_filter :clear_from_signed_in_touch, only: :destroy before_filter :clear_from_anonymous_in_touch, only: :create # ... private #   -      ip def clear_from_anonymous_in_touch $redis_onlines.del( "ip:#{request.remote_ip}" ) end #   id      - session[:user_id] #   -      id def clear_from_signed_in_touch $redis_onlines.del( "user:#{session[:user_id]}" ) end 



A few links on the topic ( English ):

Source: https://habr.com/ru/post/216047/


All Articles