Vela → smart cache per le serie temporali e non solo

Nel fintech, spesso ci troviamo a dover elaborare volumi considerevoli di dati sui tassi di cambio. Riceviamo dati da varie fonti, ognuna delle quali ha una propria interpretazione su come estrapolare i valori dei tassi per domani, dopodomani, il mese prossimo e persino per i prossimi tre anni. Se qualcuno sapesse prevedere i tassi, correttamente, sarebbe il momento di chiudere il business e semplicemente cambiare i soldi da una parte all'altra. Alcune fonti godono di maggiore fiducia, altre forniscono solo spazzatura, con rare inclusioni di valori quasi corretti, ma per coppie esotiche. Il nostro lavoro consiste nel setacciare queste decine di migliaia di valori al secondo e determinare cosa mostrare ai clienti. Dobbiamo filtrare l'unico valore corretto da tonnellate di sporcizia e fango, come fanno i fenicotteri a pranzo.

Vela → smart cache per le serie temporali e non solo

Una caratteristica distintiva dei fenicotteri è il loro grosso becco ricurvo verso il basso, con il quale filtrano il cibo dall'acqua o dal fango.
 — Wiki

Così è nata la libreria Vela, che memorizza la cache dello stato per più valori in intervalli di tempo specificati. Sotto il cofano, scarta dinamicamente i dati errati e obsoleti, e fornisce accesso agli ultimi N valori validati per ogni chiave (coppie di valute, nel nostro caso).

Supponiamo di raccogliere i tassi per tre coppie di valute. La definizione più semplice Vela per memorizzare lo stato attuale apparirà più o meno così:

defmodule Pairs do
  use Vela,
    eurusd: [sorter: &Kernel. 0
end

Aggiornamento dei valori

Vela.put/3 la funzione eseguirà sequenzialmente quanto segue:

  • chiamerà validator sul valore, se presente (vedi capitolo Validazione qui sotto);
  • aggiungerà il valore a una serie di buoni valori, se la validazione ha avuto esito positivo, o a una serie di errori :__errors__ in caso contrario;
  • chiamerà la funzione di ordinamento se sorter è definito per questa chiave, o semplicemente inserirà il valore all'inizio della lista (LIFO, vedi capitolo Ordinamento qui sotto);
  • taglierà la serie in base al parametro :limit passato alla creazione;
  • restituirà la struttura aggiornata 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]}

Anche Vela implementa Accesso, quindi si può utilizzare qualsiasi delle funzioni standard per l'aggiornamento profondo delle strutture disponibili Kernel: Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2, e Kernel.get_and_update_in/3.

Validazione

Il validatore può essere definito come:

  • una funzione esterna con un argomento (&MyMod.my_fun/1), che riceverà solo il valore da validare;
  • una funzione esterna con due argomenti, &MyMod.my_fun/2, che riceverà una coppia serie, valore da validare;
  • modulo che implementa Vela.Validator;
  • parametro di configurazione threshold, e - facoltativamente - compare_by, vedi capitolo Confronto sotto.

Se la validazione ha successo, il valore viene aggiunto alla lista sotto la chiave corrispondente, altrimenti la coppia {serie, valore} viene inviata a :__errors_.

Confronto

I valori memorizzati in queste righe possono essere qualsiasi cosa. Per insegnare Vela a confrontarli, è necessario passare compare_by parametro nella definizione della riga (a meno che i valori non possano essere confrontati standardmente Kernel.<\/2); questo parametro deve avere tipo (Vela.value() -> number()). Per impostazione predefinita è semplicemente & &1.

Inoltre, nella definizione della riga si può passare il parametro comparator per calcolare i valori delle differenze (min/max); ad esempio, passando Date.diff/2 come comparatore, si possono ottenere le differenze corrette per le date.

Un altro modo pratico di lavorare è passare il parametro threshold, che definisce il rapporto massimo consentito del nuovo valore rispetto a {min, max} intervallo. Poiché è espresso in percentuale, il controllo non utilizza comparator, ma utilizza comunque compare_by. Ad esempio, per specificare un valore soglia per il tempo delle date, è necessario specificare compare_by: &DateTime.to_unix/1 (per ottenere un valore intero) e threshold: 1, inducendo che i nuovi valori saranno consentiti solo se si trovano in ±band intervallo dai valori correnti.

Infine, si può utilizzare Vela.equal?/2 compare/2 , se i valori determinano la funzione o equal?/2, queste funzioni verranno utilizzate per il confronto, altrimenti utilizziamo semplicemente ==/2.

Recupero dei valori

La gestione dello stato attuale inizia generalmente con la chiamata Vela.purge/1, che rimuove i valori obsoleti (se validator è legato a timestamp)). Poi si può chiamare Vela.slice/1, che restituirà keyword con i nomi delle righe come chiavi e il primo, valori attuali.

Si può anche utilizzare get_in/2/pop_in/2 per accedere a valori di basso livello in ogni riga.

Applicazione

Vela può rivelarsi estremamente utile come cache per serie temporali nello stato di un processo di tipo GenServer/Agent. Vogliamo non utilizzare mai valori obsoleti dei tassi di cambio, e per questo manteniamo semplicemente un processo con stato, gestito Vela, con un validatore mostrato di seguito.

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

e Vela.purge/1 che elimina tranquillamente tutti i valori obsoleti ogni volta che abbiamo bisogno di dati. Per accedere ai valori attuali, chiamiamo semplicemente Vela.slice/1, e quando è necessaria una leggera cronologia sui tassi (l'intera serie), la restituiamo semplicemente - già ordinata - con valori validati.

Buona cache delle serie temporali!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster