计算一组选定类别的项目计数器

时间:2009-07-26 19:00:46

标签: ruby-on-rails ruby data-mining

在我们的Ruby on Rails项目中,我们有很多食谱分类标准,例如烹饪方法,场合等。每个食谱都属于这些类别中的一个或几个。当有人开始浏览食谱时,他/她可以缩小到一组特定的类别。然后我们需要计算从该集合中可访问的所有类别中的食谱数量(“可访问”表示该类别中的食谱也属于所选类别)。这与亚马逊搜索的工作方式类似:有人输入“软件”,左侧有一个菜单,上面写着“书籍(200)”,“电影(300)”等,因此用户可以通过点击这些链接深入了解。

现在我们大致就是这样实现的:

  1. 从网址构建一组选定的类别;
  2. 执行从所有符合当前所选条件的配方中提取类别ID的查询;
  3. 构建将所有类别ID映射到食谱计数的索引,并仅渲染那些具有非零计数器的索引;
  4. 将此索引存储在memcached中24小时,因此我们每天只为特定页面计算一次。
  5. 我担心的是,如果缓存未命中,构建索引可能会花费大量时间。也许您有任何建议如何解决这个问题或改进当前的解决方案?

3 个答案:

答案 0 :(得分:1)

您所描述的是一个非常糟糕的组合问题:对于每个选定的类别,迭代每个食谱,然后迭代该食谱的类别,然后返回该类别的食谱计数。即使使用优化的SQL,您也在谈论嵌套的子选择,从逻辑上讲,这不能在低于指数的时间内完成。 (这意味着当你得到很多食谱时,真的受伤了。)并且随着可能组合的数量等于(类别)^ 2,缓存变得越来越不切实际。

你确定你必须这样做吗?亚马逊,BTW你错了;他们没有像这样的“交叉类别观点”。它们显示搜索命中数,这对搜索索引很容易。在搜索框中加入“软件”并不是将软件视为一个类别;它将其视为关键字。

如果没有人要求这个功能,我建议简化它。在类别过滤器视图中,只显示匹配的所有食谱。在每个配方页面上,您可以显示该配方所在的所有类别的侧栏列表,如果您愿意,还可以计算这些类别。 (可以很容易地将其作为类别模型中的属性进行缓存,并在调出配方时通过急切加载进行检索。)

如果你由于某种原因必须这样做 - 那些要求它的用户真的想看到他们没有过滤的类别的错误印象 - 那么至少用SQL做。嵌套的子选择确实会损害并且会破坏数据库的内存,但是它们比在Ruby中执行更快。此外,还有一些Rails插件会改变缓存的行为,因此您可以在当前命中显示过期结果,然后为 next 命中重新生成缓存。

但我认真建议跟踪点击次数并确定是否有人在投入更多工作之前使用了这些点击。

答案 1 :(得分:0)

每天索引不是很干净。插入或更新数据集时,为什么不对其进行索引?

插入数据集(如食谱)

  • 启动一个线程,将内容添加到索引

  • 如果线程发生超时(如1秒)(高负载!),请将其停止

每日:

  • 将当前索引保存到磁盘

  • 更新整个索引

  • 如果失败,请从磁盘恢复保存的索引

  • 否则读取memcache的索引

答案 2 :(得分:0)

您没有对类别/产品的数量进行任何估计,但我会假设它们有很多:)

如果我想要表演,这是我的方法:(我知道,这很疯狂:))

  • 对于每个类别,在memcache中保留一个位向量,意思是:如果id为n的产品属于该类别,则第n位为1

我举个例子: 如果产品1,7,9和10属于A类,1,6,9属于B类,而1,9,11属于C,那么:

  • A是01000001 01100000
  • B是01000010 01000000
  • C是01000000 01010000

当你想要计算这些集合的交集时,只需在你的集合之间进行按位AND即可得到你的结果。

结果是:

  • 结果= A和B和C = 01000000 01000000

如果您想为每个类别进行计算,只需创建另一个类别和结果

备注:

  • 不要忘记重新计算这些 改变一些东西的载体 DB
  • 如果您打算与很多类别相交,这是非常快的
  • 对于每个类别,您必须存储大于的矢量 TOTAL_NR_OF_PRODUCTS / 8