私の提案は、関心のあるすべての間隔の最小/最大/合計を保存し、到着するすべてのデータポイントで現在の間隔に更新することです。比較のために以前のデータを読み取るときにネットワーク遅延を回避するために、Luaスクリプトを使用してRedisサーバー内で完全に読み取ることができます。
データポイントごとに(またはさらに悪いことに、データポイントフィールドごとに)1つのキーは、大量のメモリを消費します。最良の結果を得るには、それを小さなリスト/ハッシュにグループ化する必要があります(http://redis.io/topics/memory-optimizationを参照)。 Redisでは、データ構造に1レベルのネストしか許可されていません。データに複数のフィールドがあり、キーごとに複数のアイテムを保存する場合は、何らかの方法で自分でエンコードする必要があります。幸い、標準のRedis Lua環境には、非常に効率的なバイナリJSONのような形式であるmsgpackサポートが含まれています。 msgpackで「そのまま」エンコードされた例のJSONエントリは、52〜53バイトの長さになります。キーごとに100〜1000エントリになるように、時間でグループ化することをお勧めします。 1分間隔がこの要件に適合すると仮定します。その場合、キーイングスキームは次のようになります。
YYmmddHHMMSS
— tid
からのハッシュ 指定された分のmsgpackでエンコードされたデータポイントへ。5m:YYmmddHHMM
、1h:YYmmddHH
、1d:YYmmdd
— min
を含むウィンドウデータハッシュ 、max
、sum
フィールド。
1つのデータポイントを受け入れ、必要に応じてすべてのキーを更新するサンプルのLuaスクリプトを見てみましょう。 Redisスクリプトの動作方法により、スクリプトによってアクセスされるすべてのキーの名前、つまりライブデータと3つのウィンドウキーすべてを明示的に渡す必要があります。 Redis LuaにはJSON解析ライブラリも用意されているため、簡単にするために、JSONディクショナリを渡すだけであると仮定します。つまり、アプリケーション側とRedis側の2回データを解析する必要がありますが、パフォーマンスへの影響は明確ではありません。
local function update_window(winkey, price, amount)
local windata = redis.call('HGETALL', winkey)
if price > tonumber(windata.max or 0) then
redis.call('HSET', winkey, 'max', price)
end
if price < tonumber(windata.min or 1e12) then
redis.call('HSET', winkey, 'min', price)
end
redis.call('HSET', winkey, 'sum', (windata.sum or 0) + amount)
end
local currkey, fiveminkey, hourkey, daykey = unpack(KEYS)
local data = cjson.decode(ARGV[1])
local packed = cmsgpack.pack(data)
local tid = data.tid
redis.call('HSET', currkey, tid, packed)
local price = tonumber(data.price)
local amount = tonumber(data.amount)
update_window(fiveminkey, price, amount)
update_window(hourkey, price, amount)
update_window(daykey, price, amount)
この設定では、1秒あたり数千の更新を実行でき、メモリをあまり消費せず、ウィンドウデータを即座に取得できます。
更新:メモリの部分では、数百万を超えて保存したい場合は、ポイントあたり50〜60バイトがまだたくさんあります。この種のデータでは、カスタムバイナリ形式、デルタエンコーディング、およびその後のスナッピーなどを使用したチャンクの圧縮を使用して、ポイントあたり2〜3バイトまで低くすることができると思います。これを行う価値があるかどうかは、要件によって異なります。