在Redis中进行有效的数学计算

时间:2012-09-11 18:40:43

标签: 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倍的样本量,这并不可怕。如果我们的样本量大约为100万左右怎么办?

$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中获取最小/最大/长度。不幸的是,你似乎仍然需要进入应用程序,例如median,mean,std_dev。除非我们可以在Redis中计算这些。
  2. 使用Lua脚本进行计算。 (我还没有学过任何Lua,所以不能说我知道这会是什么样子。如果它可能更快,我想知道,所以我可以试试。)
  3. 利用Ruby的一种更有效的方式,这看起来有点不太可能,因为利用看似相当不错的stats gem有类似的结果
  4. 使用其他数据库。
  5. 使用StatsSample gem的示例

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

    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中发生,而不是在数据库中,我想我可能还有其他选择。

3 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

如果你可以切换到Redis 2.6,Lua脚本可能是解决这个问题的最佳方法。顺便说一下测试速度应该非常简单,所以考虑到所需的时间很少,我强烈建议你尝试使用Lua脚本来查看你得到的结果。

你可以做的另一件事是使用Lua来设置数据,并确保它还会为每个列表更新相关的哈希类型以直接保留最小/最大/平均值,所以你不必每次都计算这些统计数据,因为它们会逐步更新。并不总是可能btw,取决于您的具体用例。

答案 2 :(得分:0)

我会看看NArray。从他们的主页:

  

此扩展库包含快速计算和易于操作的大型数值数组到Ruby语言。

看起来他们的array class拥有您需要内置的大部分功能.Cmd-F“统计”在该页面上。