用户on my site为说唱歌词(example)创建注释。我想创建一个排行榜,以奖励创建最多注释的人。
排行榜应该跟踪每个用户整体创建的注释数量,以及他在过去一周,一天中创建的数量等。
我在实现整个排行榜时没有问题:
@users = User.all
<table>
<tr>
<th>Contributor</th>
<th>Annotations</th>
</tr>
<% @users.sort_by{|u| u.annotations.size }.reverse.each do |u| %>
<tr>
<td><%= u %></td>
<td><%= u.annotations.size %></td>
</tr>
<% end %>
</table>
但是当我尝试实现(比方说)每日记分牌时,我正在重复代码并且操作非常缓慢(因为它必须遍历内存中的每个注释而不是依赖于数据库排序/计数):
<table>
<tr>
<th>Contributor</th>
<th>Annotations</th>
</tr>
<% @users.sort_by{|u| u.annotations.select{|a| a.created_at > 1.day.ago }.size }.reverse.each do |u| %>
<tr>
<td><%= u %></td>
<td><%= u.annotations.select{|a| a.created_at > 1.day.ago }.size %></td>
</tr>
<% end %>
</table>
实施每日/每周记分牌的最佳方式是什么?
答案 0 :(得分:11)
排行榜作为一个整体是一个痛苦的实施。那么,根据我的经验,实际的实施是相当直接的,只是它们难以扩展。通常,您发现自己必须运行许多数据库密集的数据库查询。要处理每日/每周报告,可能会查询日期时间列,但这意味着您在所述列上有索引。该索引实际上仅对排行榜查询有用,并且它使该表上的所有其他写入付出代价,因为必须重新计算索引。
另一种方法是按计划的间隔生成统计信息,并将该数据写入单独的表,排行榜查询使用该表。例如,你有一个后台工作,每天晚上你运行一个查询(也许它是一个昂贵的,因为它不使用日期时间索引,但由于它只运行一次,并通过后台作业,费用是“ok”),该查询反过来写入确实在日期时间列上有索引的统计信息表,然后重写您的排行榜页面以点击预先计算的统计信息。根据您的需要,您可能会让cron脚本执行其他数据调整和预先计算,因此排行榜页面必须尽可能少地进行计算。
此时您的排行榜页面正常工作,当它遇到带索引的表时,仍然需要读取大量行。这假设您拥有不错的流量。在每个页面上有一个索引查询命中大量行仍然很昂贵。所以现在考虑实现页面缓存,可能在memcached中存储数据。也就是说,由于每日排行榜数据每天最少变化,因此根据定义,在每个页面视图上重新运行这些数据库查询的成本很高。将每日数据缓存在memcached中更有意义,每个页面视图只能访问memcached。
因此,您可以看到它的演变过程。如果您的流量低于您可能没有单独的表并且只在日期时间列上有索引的情况。运行总和,计数和平均值可能没问题。但它没有规模。因此,您必须考虑将其分解为更优化的结构。然后你会看到每天一遍又一遍地运行相同的查询而底层数据在24小时内没有变化是很昂贵的,所以你转到缓存设置。有许多活动部件,它可以变得复杂,好吧,真的很快很乏味。
对于排行榜而言,我是一个充满战斗力的玩世不恭的人,虽然他们非常适合游戏机制和激励人们(每个人都喜欢看分数!)但是这样做会让人很痛苦地进行大规模的工作。 / p>
答案 1 :(得分:3)
您是否考虑将这些统计信息保存在一个由观察者更新的单独表/模型中?你在这里看到了很多繁重的工作,这通常不是一种好的做法。
答案 2 :(得分:3)
我建议使用Redis。您可以执行cron类型的任务运行,从数据库中提取数据,然后将其放入Redis排序集。排序集功能可能是存储排行榜的最佳实用程序。 http://redis.io/topics/data-types
答案 3 :(得分:3)
除了杰夫关于使用redis的建议之外,这里还有一个红宝石宝石,我一直在使用我的排行榜工作,捎带redis:https://github.com/agoragames/leaderboard