Redisで数学を行うための情報をウェブで探しても、実際にはあまり見つかりません。RailsでRedis-RBgemを使用しており、結果のリストをキャッシュしています。
e = [1738738.0, 2019461.0, 1488842.0, 2272588.0, 1506046.0, 2448701.0, 3554207.0, 1659395.0, ...]
$redis.lpush "analytics:math_test", e
現在、私たちの数のリストは、1日あたりリストあたり数千から数万に達し、リストの数は1日あたり数千になる可能性があります。(これは実際にはそれほど多くはありませんが、私たちは成長しており、すぐにはるかに大きなサンプルサイズを期待しています。)
これらのリストのそれぞれについて、統計を実行できるようにしたいと思います。私は現在これをアプリ内で行っています
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
また、統計を簡単に保存することもできますが、集計リストで統計を実行するには、リストをまとめて集計する必要があります。したがって、すべての可能な集約セットではなく、生の数値を格納することは理にかなっています。このため、私は数学を速くする必要があり、これを行う方法を模索してきました。最も簡単な方法は、リストに15万個のアイテムがあるアプリ内で実行することです。これは、実際にはそれほどひどいことではありません。
$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)
1回の計算で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)
オプション
- リストのSORTメソッドを使用すると、Redisで最小/最大/長さを即座に取得できます。残念ながら、median、mean、std_devなどのアプリ内に移動する必要があるようです。これらをRedisで計算できない限り。
- Luaスクリプトを使用して計算を行います。(私はまだLuaを学んでいないので、これがどのようになるかはわかりません。もっと速いと思われる場合は、試してみたいと思います。)
- Rubyを利用するためのより効率的な方法。これは、かなりまともな統計の宝石のように見えるものを利用すると類似した結果が得られるため、少しありそうもないようです。
- 別のデータベースを使用してください。
StatsSamplegemの使用例
宝石を使用しても何も得られないようです。Pythonでは、おそらくCモジュールを作成しますが、多くのrubystatsgemが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内で行われていることを考えると、他の選択肢があるのではないかと思いました。