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.