Moja sugestia to przechowywanie wartości min/maks/suma dla wszystkich interwałów, którymi jesteś zainteresowany i aktualizowanie ich o aktualne z każdym przychodzącym punktem danych. Aby uniknąć opóźnień w sieci podczas odczytywania poprzednich danych do porównania, możesz to zrobić w całości wewnątrz serwera Redis za pomocą skryptów Lua.
Jeden klucz na punkt danych (lub, co gorsza, na pole punktu danych) zużywa zbyt dużo pamięci. Aby uzyskać najlepsze wyniki, powinieneś pogrupować je w małe listy/hasze (zobacz http://redis.io/topics/memory-optimization). Redis umożliwia tylko jeden poziom zagnieżdżania w swoich strukturach danych:jeśli dane mają wiele pól i chcesz przechowywać więcej niż jeden element na klucz, musisz jakoś samodzielnie je zakodować. Na szczęście standardowe środowisko Redis Lua zawiera obsługę msgpack, która jest bardzo wydajnym binarnym formatem podobnym do JSON. Wpisy JSON w twoim przykładzie zakodowane za pomocą msgpack "tak jak jest" będą miały długość 52-53 bajtów. Sugeruję grupowanie według czasu, aby mieć 100-1000 wpisów na klucz. Załóżmy, że interwał jednominutowy spełnia to wymaganie. Wtedy schemat kluczy wyglądałby tak:
YYmmddHHMMSS
— hash z tid
do punktów danych zakodowanych w msgpack na daną minutę.5m:YYmmddHHMM
, 1h:YYmmddHH
, 1d:YYmmdd
— skróty danych okna zawierające min
, max
, sum
pola.
Spójrzmy na przykładowy skrypt Lua, który zaakceptuje jeden punkt danych i w razie potrzeby zaktualizuje wszystkie klucze. Ze względu na sposób, w jaki działa skrypt Redis, musimy jawnie przekazać nazwy wszystkich kluczy, do których skrypt będzie miał dostęp, tj. Dane na żywo i wszystkie trzy klawisze okna. Redis Lua ma również dostępną bibliotekę parsującą JSON, więc dla uproszczenia załóżmy, że po prostu przekazujemy ją do słownika JSON. Oznacza to, że musimy analizować dane dwukrotnie:po stronie aplikacji i po stronie Redis, ale ich wpływ na wydajność nie jest jasny.
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)
Ta konfiguracja może wykonywać tysiące aktualizacji na sekundę, nie obciążając zbytnio pamięci, a dane okien mogą być natychmiast pobierane.
AKTUALIZACJA:Jeśli chodzi o pamięć, 50-60 bajtów na punkt to nadal dużo, jeśli chcesz przechowywać więcej, kilka milionów. Myślę, że przy tego rodzaju danych można uzyskać zaledwie 2-3 bajty na punkt, używając niestandardowego formatu binarnego, kodowania delta i późniejszej kompresji fragmentów za pomocą czegoś takiego jak Snappy. To zależy od Twoich wymagań, czy warto to robić.