ruby-on-rails - 在 Redis 中进行高效的数学计算

标签 ruby-on-rails math statistics redis

在网上寻找有关在 Redis 中进行数学运算的信息,但实际上并没有找到太多信息。我在 Rails 中使用 Redis-RB gem,并缓存结果列表:

e = [1738738.0, 2019461.0, 1488842.0, 2272588.0, 1506046.0, 2448701.0, 3554207.0, 1659395.0, ...]
$redis.lpush "analytics:math_test", e

目前,我们的列表数量最多为每天数千到数万个,而且每天可能有数千个列表。 (这实际上并没有那么多;但是,我们正在成长,并且预计很快就会有更大的样本量。)

对于这些列表中的每一个,我都希望能够运行统计信息。我目前在应用程序内执行此操作

def basic_stats(arr)
  return nil if arr.nil? or arr.empty?
  min = arr.min.to_f
  max = arr.max.to_f
  total = arr.inject(:+)
  len = arr.length
  mean = total.to_f / len # to_f so we don't get an integer result
  sorted = arr.sort
  median = len % 2 == 1 ? sorted[len/2] : (sorted[len/2 - 1] + sorted[len/2]).to_f / 2
  sum = arr.inject(0){|accum, i| accum +(i-mean)**2 }
  variance = sum/(arr.length - 1).to_f
  std_dev = Math.sqrt(variance).nan? ? 0 : Math.sqrt(variance)

  {min: min, max: max, mean: mean, median: median, std_dev: std_dev, size: len}
end

而且,虽然我可以简单地存储统计数据,但我经常必须将列表聚合在一起才能在聚合列表上运行统计数据。因此,存储原始数字而不是每个可能的聚合集是有意义的。正因为如此,我需要快速的数学,并且一直在探索如何做到这一点。最简单的方法就是在应用程序内执行,列表中有 150k 个项目,这实际上并不太糟糕:

$redis_analytics.llen "analytics:math_test", 0, -1
=> 156954
Benchmark.measure do
  basic_stats $redis_analytics.lrange("analytics:math_test", 0, -1).map(&:to_f)
end 
=>   2.650000   0.060000   2.710000 (  2.732993)

虽然我不想将 3 秒用于单个计算,但考虑到这可能超出我当前用例的样本数量大约 10 倍,所以这并不可怕。如果我们使用一百万左右的样本量会怎样?

$redis_analytics.llen("analytics:math_test")
=> 1063454 
Benchmark.measure do
  basic_stats $redis_analytics.lrange("analytics:math_test", 0, -1).map(&:to_f)
end
=>  21.360000   0.340000  21.700000 ( 21.847734) 

选项

  1. 在列表上使用SORT方法,然后你可以在Redis中即时获取min/max/length。不幸的是,您似乎仍然需要在应用程序中获取诸如中位数、均值、std_dev 之类的东西。除非我们可以在 Redis 中计算这些。
  2. 使用 Lua 脚本进行计算。 (我还没有学过任何 Lua,所以不能说我知道这会是什么样子。如果它可能更快,我想知道所以我可以试试。)
  3. 使用 Ruby 的一些更有效的方法,这似乎有点不太可能,因为使用看起来相当不错的 stats gem 有类似的结果
  4. 使用不同的数据库。

使用 StatsSample gem 的示例

使用 gem 似乎对我没有任何好处。在 Python 中,我可能会编写一个 C 模块,不确定 C 中是否有很多 ruby​​ stats gems。

require 'statsample'
def basic_stats(stats)
  return nil if stats.nil? or stats.empty?
  arr = stats.to_scale

  {min: arr.min, max: arr.max, mean: arr.mean, median: arr.median, std_dev: arr.sd, size: stats.length}
end

Benchmark.measure do
  basic_stats $redis_analytics.lrange("analytics:math_test", 0, -1).map(&:to_f)
end
=>  20.860000   0.440000  21.300000 ( 21.436437)

结尾

当然,这么大的统计计算很可能会花费很长时间,我应该将它们卸载到一个队列中。然而,考虑到大部分数学运算实际上发生在 Ruby/Rails 内部,而不是数据库中,我想我可能还有其他选择。

最佳答案

我想保持这个开放状态,以防任何人有任何意见可以帮助其他人做同样的事情。然而,对我来说,我刚刚意识到我花了太多时间试图强制 Redis 做一些 SQL 做得很好的事情。如果我只是将其转储到 Postgres 中,我就可以直接在数据库中进行非常高效的聚合和数学运算。我想我只是坚持使用 Redis 做一些事情,当它开始时,这是个好主意,但扩展到一些不好的地方。

关于ruby-on-rails - 在 Redis 中进行高效的数学计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12375926/

相关文章:

c++ - 给定一个大的二维矩阵,我可以唯一地适合多少个较小尺寸 n 的二维矩阵?

algorithm - 蛋糕比较算法

web-applications - 实现 'popular content' 显示的最佳方式是什么?

ruby-on-rails - capybara ::无限重定向错误

ruby-on-rails - 如何使用 mongoid/mongodb 批量更新/更新插入?

css - 当响应式 Bootstrap 导航栏扩展到全尺寸时滚动不起作用

angularjs - AngularDart 中的数学函数

math - J 函数不起作用

ruby-on-rails - ruby /rails : get elements from array where indices are divisible by x

database - Oracle 数据库统计应该多久运行一次?