大表上的Rails计算

时间:2016-12-01 19:20:51

标签: ruby-on-rails ruby postgresql rails-activerecord tf-idf

我会尝试解释我的问题。 我已根据购买情况创建了一个表格,用于表示用户的文字模型( user_models )。我还有一个表 term_tfs ,它将user_id和term(varchar(200))存储为PK加上一些其他数字列。它基本上是一个带有术语的矩阵,它们的数值为模型的tf_idf_norm值。现在我需要进行比较用户模型的计算,因此我需要为一个用户加载此矩阵,并将其与其他用户进行比较。

问题是,term_tfs表非常大(cca.13.5 mill。行),我需要为至少5(1285个用户)或10(9333)次购买的用户获取矩阵。当我从term_tfs表中选择一个时,它大约需要20-40毫秒。但我需要一些方法来为其他9千位用户进行比较。将每个user_id查询到term_tfs中的朴素方法需要10秒以上才能进行一次比较,如果我想为接下来的几千个用户进行这种比较并将其存储在其他地方,这种方法很慢。

def self.compare_user(user_id)
  @results = Hash.new
  # @user_ids = UserModel.where.not(user_id: user_id).pluck(:user_id)
  @user_ids = UserModel.get_useful_ids(user_id, 5)
  @user_matrix = TermTf.where(user_id: user_id).pluck(:term,  :tf_idf_norm)
  @user_terms = @user_matrix.map { |a| a[0] }


  @user_ids.each do |id|

    matrix = TermTf.where(user_id: id).pluck(:term, :tf_idf_norm)
    store_result( compare_matrix(matrix), id )

  end

  sort_results( @results )
end

def self.compare_matrix(matrix)
  sim = 0

  matrix.each do |t|
    unless ( ( i = @user_terms.index(t[0]) ).nil? )
      sim += t[1] * @user_matrix[i][1]
    end
  end

  sim
end

def self.store_result(similarity, id)
  @results[id] = similarity
end

基准输出(9333 user_ids):

puts Benchmark.measure {@user_ids.each{|id| TermTf.where(user_id:  id).pluck(:term, :tf_idf_norm)}}
4.890000   0.180000   5.070000 ( 11.019708)

这似乎是相当糟糕/缓慢的方法,那么如何让它更快?我很高兴听到其他方法如何使用Ruby或SQL来解决这个问题。

2 个答案:

答案 0 :(得分:2)

要将Beartech的方法放入Rails代码而不是创建视图,您可以做一些这样的事情(需要根据您的需要进行调整):

subquery = TermTf.where(user_id: user_id).select(:term,  :tf_idf_norm).to_sql
result = TermTf.joins("INNER JOIN (#{subquery }) sub on sub.term = term_tfs.term")
         .select("term_tfs.user_id as user_id, sum(sub.tf_idf_norm * term_tfs.tf_idf_norm) as tf_idf_norm_sum")
         .where(user_id: @user_ids)
         .where.not(user_id:  user_id)
         .group('term_tfs.user_id')
         .all

答案 1 :(得分:1)

我的回答是不要在Rails中这样做。你最后说你想知道如何在Ruby中做到这一点,但我希望你会考虑一个非ru​​by的答案。如果它在Rails中很慢,那是因为Rails不是很好的"在那个特定的过程中。我有几个大桌子,我必须在我的应用程序中显示。如果您观看Rails控制台并显示大量数据库请求或性能下降,则应将该过程移至数据库。 dB设计人员多年来一直在调整数据库以处理这些流程。

我会在SQL中重新创建相同的逻辑并将其添加为数据库中的视图。然后,您可以添加一个简单的模型,如:

在你的模特中

term_tfs_view.rb

class TermTfsView  < ActiveRecord::Base
   #this is a model for a view in the DB
end

在您的数据库中命名您的表term_tfs_views,它会自动将此模型与表关联。

我的SQL技能很简陋,否则我会试着给你一个从Ruby / Rails翻译成SQL的逻辑的例子。如果SQL专家可以权衡并告诉我们使用SQL是否切实可行,那将会有所帮助。

重要

视图非常适合您要查看的数据。你不能做更新/插入/等。使用视图支持的模型。但这并不意味着它们不适合将繁重的Rails从Rails转移到数据库。此链接很好地解释了View支持模型的想法:enter image description here

相关问题