W fintechu często musimy przetwarzać dość masywne ilości danych dotyczących kursów walut. Otrzymujemy dane z różnych źródeł, a każde z nich ma swoje własne wyobrażenie o tym, jak ekstrapolować wartości kursów na jutro, pojutrze, następny miesiąc, a nawet na kolejne trzy lata. Gdyby ktoś potrafił przewidywać kursy, prawidłowomożna by zamknąć interes i po prostu zamieniać pieniądze tam i z powrotem. Niektóre źródła cieszą się większym zaufaniem, inne dostarczają same śmieci, z rzadkimi wstawkami niemal poprawnych wartości, ale za to dla egzotycznych par. Nasza praca polega na tym, aby przesiać te dziesiątki tysięcy wartości na sekundę i określić, co dokładnie pokazać klientom. Musimy odfiltrować jedną właściwą wartość z tony brudu i mułu, jak robią to flamingi podczas posiłku.

Szczególną cechą flamingów jest masywny, wygięty w dół dziób, za pomocą którego filtrują pokarm z wody lub mułu.
—
Tak narodziła się biblioteka , która przechowuje cache stanu dla wielu wartości w zadanych przedziałach czasowych. Pod maską na bieżąco odrzuca złe i przestarzałe dane oraz zapewnia dostęp do ostatnich N zweryfikowanych wartości dla każdego klucza (pary walut, w naszym przypadku).
Załóżmy, że zbieramy kursy dla trzech par walut. Najprostsza definicja Vela dla przechowywania aktualnego stanu będzie wyglądać mniej więcej tak:
defmodule Pairs do
use Vela,
eurusd: [sorter: &Kernel. 0
endAktualizacja wartości
funkcja kolejno wykona następujące:
- wywoła
validatorna wartości, jeśli taki jest zdefiniowany (patrz rozdział Walidacja poniżej); - doda wartość albo do szeregu dobrych wartości, jeśli walidacja zakończyła się pomyślnie, albo do szeregów pomocniczych
:__errors__w przeciwnym razie; - wywoła sortowanie, jeśli
sorterjest zdefiniowany dla tego klucza, lub po prostu umieści wartość na początku listy (LIFO, patrz rozdział Sortowanie poniżej); - obetnie szereg zgodnie z parametrem
:limitprzekazanym podczas tworzenia; - zwróci zaktualizowaną strukturę
Vela.
iex|1 > pary = %Pairs{}
iex|2 > Vela.put(pary, :eurcad, 1.0)
#⇒ %Pairs{..., eurcad: [1.0], ...}
iex|3 > Vela.put(pary, :eurcad, -1.0)
#⇒ %Pairs{__errors__: [eurcad: -1.0], ...}
iex|4 > pary |> Vela.put(:eurusd, 2.0) |> Vela.put(:eurusd, 1.0)
#⇒ %Pairs{... eurusd: [1.0, 2.0]}Również Vela implementuje , więc można skorzystać z dowolnej z standardowych funkcji do głębokiej aktualizacji struktur z arsenału : , , , , i .
Walidacja
Walidator może być zdefiniowany jako:
- zewnętrzna funkcja z jednym argumentem (
&MyMod.my_fun/1), otrzyma tylko wartość do walidacji; - zewnętrzna funkcja z dwoma argumentami,
&MyMod.my_fun/2, otrzyma paręserie, wartośćdo walidacji; - moduł implementujący ;
- parametr konfiguracyjny
threshold, i – opcjonalnie –compare_by, patrz rozdział Porównanie poniżej.
Jeśli walidacja przebiegnie pomyślnie, wartość zostanie dodana do listy pod odpowiednim kluczem, w przeciwnym razie krotka {serie, wartość} zostanie wysłana do :__errors_.
Porównanie
Wartości przechowywane w tych seriach mogą być dowolne. Aby nauczyć Vela je porównywać, należy przekazać compare_by parametr w definicji serii (chyba że wartości mogą być porównywane standardowym Kernel.</2); parametr ten powinien mieć typ (Vela.value() -> number()). Domyślnie to po prostu & &1.
Ponadto, w definicji serii można przekazać parametr comparator do obliczania wartości delta (min/max); na przykład, przekazując Date.diff/2 jako komparator, można uzyskać poprawne delty dla dat.
Innym wygodnym sposobem działania jest przekazywanie parametru threshold, który określa maksymalne dopuszczalne odniesienie nowej wartości do {min, max} przedziału. Ponieważ jest on określony w procentach, walidacja nie korzysta z comparator, ale nadal wykorzystuje compare_by. Na przykład, aby określić próg dla czasów dat, należy podać compare_by: &DateTime.to_unix/1 (do uzyskania wartości całkowitej) i threshold: 1, w wyniku czego nowe wartości będą dozwolone tylko wtedy, gdy mieszczą się w ±band przedziale od obecnych wartości.
Na koniec, można użyć Vela.equal?/2 do porównania dwóch pamięci podręcznych. Jeśli wartości definiują funkcję equal?/2 lub compare/2, to te funkcje zostaną użyte do porównania, w przeciwnym razie po prostu używamy ==/2.
Uzyskiwanie wartości
Przetwarzanie bieżącego stanu zwykle zaczyna się od wywołania Vela.purge/1, które usuwa przestarzałe wartości (jeśli validator jest uzależnione od timestamps). Następnie można wywołać Vela.slice/1, która zwraca keyword z nazwami wierszy jako klucze oraz pierwszymi, aktualnymi wartościami.
Można też skorzystać z get_in/2/pop_in/2 do niskopoziomowego dostępu do wartości w każdym wierszu.
Aplikacja
Vela może okazać się niezwykle przydatna jako pamięć podręczna szeregów czasowych w stanie procesu typu GenServer/Agent. Chcemy nigdy nie używać przestarzałych wartości kursów, dlatego po prostu utrzymujemy proces ze stanem, obsługiwanym Vela, z walidatorem pokazanym poniżej.
@impl Vela.Validator
def valid?(_key, %Rate{} = rate),
do: Rate.age(rate) < @death_agei Vela.purge/1 spokojnie usuwa wszystkie przestarzałe wartości za każdym razem, gdy potrzebujemy danych. Aby uzyskać aktualne wartości, po prostu wywołujemy Vela.slice/1, a kiedy potrzebna jest krótka historia kursu (cały szereg), po prostu zwracamy go – już posortowanego – z zwalidowanymi wartościami.
Sukcesów w pamięci podręcznej szeregów czasowych!
Źródło: habr.com
