Rails 大型计算 table
Rails computation on large table
我会尽力解释我的问题。
我创建了一个 table,它表示基于购买的用户文本模型(user_models)。我还有一个 table term_tfs 存储 user_id 和 term(varchar(200)) 作为 PK 加上一些其他数字列。它基本上是一个矩阵,其中包含术语及其模型的数字 tf_idf_norm 值。现在我需要做一个比较用户模型的计算,所以我需要为一个用户加载这个矩阵,并与不同用户的其他矩阵进行比较。
问题是,term_tfs table 确实很大(大约 13.5 行),我需要为至少有 5(1285 个用户)或 10( 9333) 采购。当我从 term_tfs table 制作一个 select 时,大约需要 20-40 毫秒。但是我需要一些方法来让其他 9000 名用户进行比较。将每个 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)
这似乎是 bad/slow 方法,那么如何让它更快呢?我也很想听听其他解决这个问题的方法,Ruby 或 SQL。
我的回答是在 Rails 中不这样做。你在最后说你想知道如何在 Ruby 中做到这一点,但我希望你能考虑一个非 ruby 的答案。如果它在 Rails 中很慢,那是因为 Rails 在那个特定的过程中不是 "good"。我有几个必须在我的应用程序中显示的大 table。如果您观察 Rails 控制台并且它显示大量数据库请求或性能低下,您应该将该进程移至数据库。多年来,dB 设计人员一直在调整 DB 以处理这些过程。
我会在 SQL 中重新创建相同的逻辑并将其作为视图添加到您的数据库中。然后您可以添加一个简单的模型,例如:
在你的模型中
term_tfs_view.rb
class TermTfsView < ActiveRecord::Base
#this is a model for a view in the DB
end
在您的数据库中命名您的 table term_tfs_views
,它会自动将此模型与 table 相关联。
我的 SQL 技能很初级,否则我会尝试给你一个例子,将你的逻辑从 Ruby/Rails 翻译成 SQL。如果 SQL 专家可以权衡并让我们知道使用 SQL 这样做是否可行,那将会有所帮助。
重要
视图非常适合您要查看的数据。你做不到 updates/inserts/etc。使用视图支持模型。但这并不意味着它们不适合将繁重的工作从 Rails 转移到数据库。 link 很好地解释了视图支持模型的概念:https://dan.chak.org/enterprise-rails/chapter-11-view-backed-models/
要将 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
我会尽力解释我的问题。 我创建了一个 table,它表示基于购买的用户文本模型(user_models)。我还有一个 table term_tfs 存储 user_id 和 term(varchar(200)) 作为 PK 加上一些其他数字列。它基本上是一个矩阵,其中包含术语及其模型的数字 tf_idf_norm 值。现在我需要做一个比较用户模型的计算,所以我需要为一个用户加载这个矩阵,并与不同用户的其他矩阵进行比较。
问题是,term_tfs table 确实很大(大约 13.5 行),我需要为至少有 5(1285 个用户)或 10( 9333) 采购。当我从 term_tfs table 制作一个 select 时,大约需要 20-40 毫秒。但是我需要一些方法来让其他 9000 名用户进行比较。将每个 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)
这似乎是 bad/slow 方法,那么如何让它更快呢?我也很想听听其他解决这个问题的方法,Ruby 或 SQL。
我的回答是在 Rails 中不这样做。你在最后说你想知道如何在 Ruby 中做到这一点,但我希望你能考虑一个非 ruby 的答案。如果它在 Rails 中很慢,那是因为 Rails 在那个特定的过程中不是 "good"。我有几个必须在我的应用程序中显示的大 table。如果您观察 Rails 控制台并且它显示大量数据库请求或性能低下,您应该将该进程移至数据库。多年来,dB 设计人员一直在调整 DB 以处理这些过程。
我会在 SQL 中重新创建相同的逻辑并将其作为视图添加到您的数据库中。然后您可以添加一个简单的模型,例如:
在你的模型中
term_tfs_view.rb
class TermTfsView < ActiveRecord::Base
#this is a model for a view in the DB
end
在您的数据库中命名您的 table term_tfs_views
,它会自动将此模型与 table 相关联。
我的 SQL 技能很初级,否则我会尝试给你一个例子,将你的逻辑从 Ruby/Rails 翻译成 SQL。如果 SQL 专家可以权衡并让我们知道使用 SQL 这样做是否可行,那将会有所帮助。
重要
视图非常适合您要查看的数据。你做不到 updates/inserts/etc。使用视图支持模型。但这并不意味着它们不适合将繁重的工作从 Rails 转移到数据库。 link 很好地解释了视图支持模型的概念:https://dan.chak.org/enterprise-rails/chapter-11-view-backed-models/
要将 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