简体   繁体   中英

Leaderboard ranking via elasticsearch (with tire)

I have a mapping that boils down to the following (unrelated fields removed):

mapping 
  indexes :id, type: 'integer', index: :not_analyze        
  indexes :first_name, boost: 5, type: 'string', analyzer: 'snowball'
  indexes :votes, type: 'integer', index: :not_analyzed
end

At the moment I'm calculating ranking via postgres, so that given the following entries:

| first_name | votes |
----------------------
| Andy       |     5 |
| Barry      |     8 |
| Carl       |     5 |
| Derek      |     1 |

Using postgres, I can get the following:

| first_name | votes | rank |
-----------------------------
| Barry      |     8 |    1 |
| Andy       |     5 |    2 |
| Carl       |     5 |    2 |
| Derek      |     1 |    4 |

Is it possible somehow calculate this ranking via elasticsearch?

I don't believe ElasticSearch is the place to do it, since updating a single document would require recalculation of all ranking values. Not possible, as far as I can tell.

Instead, once you get the results you can use Ruby to calculate the ranking with something like this:

scores = {:a=>5, :b=>8, :c=>5, :d=>1}
scores.values.sort{|a,b| a <=> b}.tap do |sorted_scores|
  sorted_scores.each{|vote| puts sorted_scores.index(vote)+1 }
end

Redis is indeed an ideal solution for leaderboards. While it is introducing another technology, if you're using AWS, note that ElastiCache managed Redis was just launched this week .

Generic Redis commands would be:

zadd votes 5 "Andy"
zadd votes 8 "Barry"
zadd votes 5 "Carl"
zadd votes 1 "Derek"

Then to get the leaderboard with most votes as highest ranked:

zrevrange votes 0 -1

Refer to the Redis docs for ZREVRANGE for more details.

For Ruby on Rails, I'd encourage you to look at my redis-objects gem which is popular since it easily integrates with ActiveRecord. Assuming you have a table with a votes column as shown, you could update the ranking on save:

class User < ActiveRecord::Base
  include Redis::Objects
  sorted_set :rank, global: true

  after_save :update_rank
  def update_rank
    self.class.rank[id] = votes
  end
end

Then retrieve the leaderboard:

User.rank.revrange(0, -1)

In this example, this will return id values which you could then use to retrieve records as follows. (You could also store first_name or some other unique value.)

ids = User.rank.revrange(0, -1)
users = User.where(id: ids).all

You can paginate through results with revrange by passing different start/end values:

User.rank.revrange(0, 9)
User.rank.revrange(10, 19)

You could easily wrap this in a self. method in User that retrieved a page of rankings from Redis and returned the DB records accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM