Vela → cache inteligente para séries temporais e muito mais

No setor financeiro, frequentemente lidamos com grandes volumes de dados sobre taxas de câmbio. Recebemos informações de diversas fontes, cada uma com sua própria interpretação sobre como extrapolar os valores das taxas para amanhã, depois de amanhã, o próximo mês e até os próximos três anos. Se alguém conseguisse prever as taxas, armazenar corretamente, seria hora de fechar o negócio e simplesmente trocar dinheiro de forma arbitrária. Algumas fontes são mais confiáveis, enquanto outras fornecem apenas lixo, com raras exceções de valores quase corretos, mas para pares exóticos. Nossa tarefa é filtrar essas dezenas de milhares de valores por segundo e determinar o que exatamente mostrar aos clientes. Precisamos separar o único valor correto de uma tonelada de sujeira e lama, como fazem os flamingos durante a alimentação.

Vela → cache inteligente para séries temporais e muito mais

Uma característica distintiva dos flamingos é seu grande bico curvado para baixo, que utilizam para filtrar alimentos da água ou da lama.
 — Wiki

Foi assim que nasceu a biblioteca Vela, que armazena um cache de estados para vários valores em intervalos de tempo definidos. Sob o capô, ela filtra em tempo real dados ruins e desatualizados, além de fornecer acesso aos últimos N valores validados para cada chave (pares de moedas, no nosso caso).

Suponha que coletamos taxas para três pares de moedas. A definição mais simples Vela para armazenar o estado atual poderia ser algo assim:

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

Atualização de valores

Vela.put/3 a função fará o seguinte de forma sequencial:

  • chamará o validator no valor, se este estiver definido (veja o capítulo Validação abaixo);
  • adicionará o valor a uma lista de bons valores, se a validação for bem-sucedida, ou a uma lista de erro :__errors__ caso contrário;
  • chamará a ordenação se um sorter estiver definido para esta chave, ou simplesmente colocará o valor no início da lista (LIFO, veja o capítulo Classificação abaixo);
  • cortará a lista de acordo com o parâmetro :limit passado durante a criação;
  • retornará a estrutura atualizada 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]}

Além disso Vela implementa Acesso, então você pode usar qualquer uma das funções padrão para atualizar estruturalmente os valores a partir do arsenal Kernel: Kernel.get_in/2, Kernel.put_in/3, Kernel.update_in/3, Kernel.pop_in/2, e Kernel.get_and_update_in/3.

Validação

Um validador pode ser definido como:

  • uma função externa com um argumento (&MyMod.my_fun/1), ela receberá apenas o valor para validação;
  • uma função externa com dois argumentos, &MyMod.my_fun/2, ela receberá o par serie, valor para validação;
  • um módulo que implementa Vela.Validator;
  • parâmetro de configuração threshold, e — opcionalmente — compare_by, veja o capítulo Comparação abaixo.

Se a validação for bem-sucedida, o valor é adicionado à lista sob a chave correspondente, caso contrário, a tuple {serie, valor} é enviada para :__errors_.

Comparação

Os valores armazenados nessas linhas podem ser quaisquer. Para ensinar Vela eles a comparar, é necessário passar compare_by um parâmetro na definição da série (a menos que os valores não possam ser comparados usando o padrão Kernel.</2); esse parâmetro deve ter o tipo (Vela.value() -> number()). Por padrão, é apenas & &1.

Além disso, na definição da série, pode-se passar o parâmetro comparator para calcular os valores das diferenças (min/max); por exemplo, passando Date.diff/2 como comparador, você pode obter as diferenças corretas para as datas.

Outra maneira conveniente de trabalhar é passando um parâmetro threshold, que define a relação máxima permitida do novo valor ao {min, max} intervalo. Como é definido em porcentagens, a verificação não usa comparator, mas ainda utiliza compare_by. Por exemplo, para definir um valor limite para o tempo das datas, é necessário especificar compare_by: &DateTime.to_unix/1 (para obter um valor inteiro) e threshold: 1, resultando em novos valores que serão permitidos apenas se estiverem dentro do ±banda intervalo dos valores atuais.

Finalmente, você pode usar Vela.equal?/2 para comparar dois caches. Se os valores definirem uma função equal?/2 ou compare/2, então essas funções serão usadas para a comparação, caso contrário, usaremos simplesmente ==/2.

Obtenção de valores

O processamento do estado atual geralmente começa com a chamada Vela.purge/1, que remove valores obsoletos (se o validator atado a timestamps). Em seguida, você pode chamar Vela.slice/1, que retornará palavra-chave com os nomes das linhas como chaves e os primeiros valores atualizados.

Você também pode usar get_in/2/pop_in/2 para acesso de baixo nível aos valores em cada linha.

Aplicativo

Vela pode ser extremamente útil como cache de séries temporais no estado de um processo do tipo GenServer/Agente. Não queremos nunca usar valores desatualizados das taxas, e para isso mantemos o processo com estado, processando Vela, com o validador mostrado abaixo.

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

e Vela.purge/1 remove tranquilamente todos os valores desatualizados sempre que precisamos de dados. Para acessar os valores atuais, basta chamar Vela.slice/1, e quando precisamos de um pequeno histórico da taxa (toda a série), simplesmente o retornamos - já ordenado - com os valores validados.

Boa sorte com o cache de séries temporais!

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster