Vela → разумны кэш для time series і не толькі

У фінтэху нам часта даводзіцца апрацоўваць даволі масіўныя аб'ёмы дадзеных курсаў абмену валют. Мы атрымліваем дадзеныя з розных крыніц, і кожны з іх мае ўласнае ўяўленне аб тым, як экстрапаляваць значэння курсаў на заўтра, паслязаўтра, наступны месяц і нават наступныя тры гады. Калі б нехта ўмеў прадказваць курсы правільна, своечасова было б зачыняць бізнэс і проста тупа мяняць грошы туды-сюды. Некаторыя крыніцы карыстаюцца вялікім даверам, некаторыя пастаўляюць запар смецце, з рэдкімі украпінамі амаль правільных значэнняў, але затое для экзатычных пар. Наша праца заключаецца ў тым, каб прасеяць гэтыя дзясяткі тысяч значэнняў у секунду і вызначыць, што менавіта паказаць заказчыкам. Нам трэба адфільтраваць адзінае правільнае значэнне з тоны бруду і глею, як гэта робяць фламінга на абедзе.

Vela → разумны кэш для time series і не толькі

Адмысловай адметнай прыкметай фламінга з'яўляецца масіўная выгінастая ўніз дзюба, з дапамогай якога яны фільтруюць ежу з вады ці глею.
 - Вікі

Так нарадзілася бібліятэка Vela, якая захоўвае кэш стану для некалькіх значэнняў у зададзеных часавых інтэрвалах. Пад капотам яна на лёце адсейвае дрэнныя і састарэлыя дадзеныя, а таксама дае доступ да апошніх. N якія прайшлі валідацыю значэнням для кожнага ключа (пары валют, у нашым выпадку).

Дапусцім, мы збіраем курсы для трох пар валют. Найпростае вызначэнне Vela для захоўвання актуальнага стану будзе выглядаць неяк так:

defmodule Pairs do
  use Vela,
    eurusd: [sorter: &Kernel.<=/2],
    eurgbp: [limit: 3, errors: 1],
    eurcad: [validator: Pairs]

  @behaviour Vela.Validator

  @impl Vela.Validator
  def valid?(:eurcad, rate), do: rate > 0
end

Абнаўленне значэнняў

Vela.put/3 функцыя паслядоўна зробіць наступнае:

  • выкліча validator на значэнні, калі такі вызначаны (гл. глаўку Валідацыя ніжэй);
  • дадасць значэнне або ў шэраг добрых значэнняў, калі валідацыя скончылася паспяхова, або ў службовы шэраг :__errors__ у адваротным выпадку;
  • выкліча сартаванне калі sorter вызначаны для дадзенага ключа, ці проста пакладзе значэнне ў галаву спісу (LIFO, гл. глаўку Сартаванне ніжэй);
  • абрэжа шэраг у адпаведнасці з параметрам :limit перададзеным пры стварэнні;
  • верне абноўленую структуру Vela.

iex|1 > pairs = %Pairs{}
iex|2 > Vela.put(pairs, :eurcad, 1.0)
#⇒ %Pairs{..., eurcad: [1.0], ...}
iex|3 > Vela.put(pairs, :eurcad, -1.0)
#⇒ %Pairs{__errors__: [eurcad: -1.0], ...}
iex|4 > pairs |> Vela.put(:eurusd, 2.0) |> Vela.put(:eurusd, 1.0)
#⇒ %Pairs{... eurusd: [1.0, 2.0]}

Таксама Vela імплементуе Access, так што можна для абнаўлення значэнняў скарыстацца любой з стандартных функцый для глыбокага абнаўлення структур з арсенала Kernel: Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2, і Kernel.get_and_update_in/3.

Валідацыя

Валідатар можа быць вызначаны як:

  • знешняя функцыя з адным аргументам (&MyMod.my_fun/1), яна атрымае толькі значэнне для валідацыі;
  • знешняя функцыя з двума аргументамі, &MyMod.my_fun/2, яна атрымае пару serie, value для валідацыі;
  • модуль, які імплементуе Vela.Validator;
  • канфігурацыйны параметр threshold, І - апцыянальна - compare_by, гл. глаўку параўнанне ніжэй.

Калі валідацыя прайшла паспяхова, значэнне дадаецца ў спіс пад адпаведным ключом, у адваротным выпадку картэж {serie, value} адпраўляецца ў :__errors_.

параўнанне

Значэнні, якія захоўваюцца ў гэтых шэрагах, могуць быць любымі. Каб навучыць Vela іх параўноўваць, неабходна перадаць compare_by параметр у вызначэнне шэрагу (калі толькі значэння не могуць быць параўнаны стандартным Kernel.</2); гэты параметр павінен мець тып (Vela.value() -> number()). Па змаўчанні гэта проста & &1.

Таксама, у вызначэнне шэрагу можна перадаць параметр comparator для вылічэння значэнняў дэльт (min/max); напрыклад, перадаючы Date.diff/2 у якасці кампаратара, можна атрымаць правільныя дэльты для дат.

Іншым зручным спосабам працы з'яўляецца перадача параметра threshold, які вызначае максімальна дапушчальнае стаўленне новага значэння да {min, max} інтэрвалу. Паколькі ён зададзены ў працэнтах, праверка не выкарыстоўвае comparator, але ўсё яшчэ выкарыстоўвае compare_by. Напрыклад, каб паказаць парогавае значэнне для часу дат, неабходна паказаць compare_by: &DateTime.to_unix/1 (для атрымання цэлалікавага значэння) і threshold: 1, у выніку чаго новыя значэння будуць дазволеныя, толькі калі яны знаходзяцца ў ±band інтэрвале ад бягучых значэнняў.

Нарэшце, можна выкарыстоўваць Vela.equal?/2 для параўнання двух кэшаў. Калі значэння вызначаюць функцыю equal?/2 або compare/2, то гэтыя функцыі будуць выкарыстаны для параўнання, у адваротным выпадку мы тупа выкарыстоўваем ==/2.

Атрыманне значэнняў

Апрацоўка бягучага стану звычайна пачынаецца з выкліку Vela.purge/1, які прыбірае састарэлыя значэння (калі validator завязаны на timestamps). Затым можна выклікаць Vela.slice/1, якая верне keyword з імёнамі шэрагаў у якасці ключоў і першым, актуальнымі значэннямі.

Таксама можна скарыстацца get_in/2/pop_in/2 для нізкаўзроўневага доступу да значэнняў у кожным шэрагу.

Дадатак

Vela можа апынуцца надзвычай карыснай у якасці кэша часовых шэрагаў у стейце працэсу тыпу GenServer/Agent. Мы хочам ніколі не выкарыстоўваць састарэлыя значэння курсаў, і для гэтага мы проста трымаем працэс са станам, апрацоўваным Vela, з валідатарам, паказаным ніжэй.

@impl Vela.Validator
def valid?(_key, %Rate{} = rate),
  do: Rate.age(rate) < @death_age

и Vela.purge/1 спакойна выдаляе ўсе састарэлыя значэння кожны раз, калі нам патрабуюцца дадзеныя. Для доступу да актуальных значэнняў мы проста выклікаем Vela.slice/1, а калі патрабуецца невялікая гісторыя па курсе (увесь шэраг цалкам), мы проста вяртаем яго - ужо адсартаваным - з праваліраванымі значэннямі.

Удалага кэшавання часавых шэрагаў!

Крыніца: habr.com

Дадаць каментар