Como fizemos o núcleo do negócio de investimentos do Alfa-Bank baseado no Tarantool


Uma filmagem do filme “Nosso Universo Secreto: A Vida Oculta da Célula” O

negócio de investimentos é uma das áreas mais difíceis do mundo bancário, porque não existem apenas empréstimos, empréstimos e depósitos, mas também títulos, moeda, bens, derivativos e todos os tipos de dificuldades na forma de produtos estruturais.

Recentemente, vimos um aumento na alfabetização financeira da população. Mais e mais pessoas estão envolvidas na negociação nos mercados de valores mobiliários. Contas de investimento individuais apareceram não há muito tempo. Eles permitem negociar nos mercados de valores mobiliários e, ao mesmo tempo, recebem deduções fiscais ou não pagam impostos. E todos os clientes que nos procuram desejam gerenciar seu portfólio e ver relatórios em tempo real. Além disso, na maioria das vezes esse portfólio é multiproduto, ou seja, as pessoas são clientes de várias linhas de negócios.

Além disso, os requisitos dos reguladores, russos e estrangeiros, estão aumentando.

Para atender às necessidades atuais e estabelecer as bases para futuras atualizações, desenvolvemos o núcleo do negócio de investimentos baseado no Tarantool.

Algumas estatísticas. Os negócios de investimento do Alfa-Bank fornecem serviços de corretagem para pessoas físicas e jurídicas, oferecendo a oportunidade de negociar em vários mercados de valores mobiliários, serviços de custódia para armazenamento de valores mobiliários, serviços de gerenciamento de confiança para indivíduos com capital privado e grande, serviços de emissão de títulos para outras companhias. O negócio de investimentos do Alfa-Bank é superior a 3 mil cotações por segundo, que são baixadas de várias plataformas de negociação. Durante o dia útil nos mercados, mais de 300 mil transações são concluídas em nome do banco ou de seus clientes. Nas plataformas externa e interna, são executados até 5 mil pedidos por segundo. Ao mesmo tempo, todos os clientes, internos e externos, desejam ver suas posições em tempo real.

fundo


Em algum lugar desde o início dos anos 2000, nossas áreas de negócios de investimentos se desenvolveram de forma independente: negociação de câmbio, serviços de corretagem, negociação de moeda, negociação de balcão e valores mobiliários e diversos derivativos. Como resultado, caímos na armadilha dos poços funcionais. O que é isso? Cada linha de negócios tem seus próprios sistemas que duplicam as funções uma da outra. Cada sistema possui seu próprio modelo de dados, embora eles operem com os mesmos conceitos: transações, instrumentos, contrapartes, cotações e muito mais. E como cada sistema se desenvolveu de forma independente, surgiu um zoológico diversificado de tecnologias.

Além disso, a base de código dos sistemas já está desatualizada, porque alguns produtos se originaram em meados da década de 90. E em algumas áreas, atrasou o processo de desenvolvimento, houve problemas com a produtividade.

Novos requisitos de solução


Os negócios perceberam que o desenvolvimento tecnológico é vital para o desenvolvimento. Foram-nos atribuídas as tarefas:

  1. Colete todos os dados comerciais em um único armazenamento rápido e em um único modelo de dados.
  2. Não devemos perder ou modificar essas informações.
  3. É necessário fazer a versão dos dados, pois a qualquer momento o regulador pode solicitar estatísticas dos anos anteriores.
  4. Não devemos apenas trazer alguns DBMS novos e modernos, mas criar uma plataforma para resolver problemas de negócios.

Além disso, nossos arquitetos definem suas condições:

  1. A nova solução deve ser de classe corporativa, ou seja, já deve ser testada em algumas grandes empresas.
  2. O modo de operação da solução deve ser de missão crítica. Isso significa que devemos estar presentes ao mesmo tempo em vários data centers e experimentar com calma a desconexão de um data center.
  3. . , , - . , .
  4. , .

Seguimos o caminho padrão: formulamos requisitos e contatamos o departamento de compras. A partir daí, temos uma lista de empresas que, em geral, estão prontas para isso. Eles disseram a todos sobre a tarefa e seis deles receberam uma avaliação das soluções.

Nós do banco não acreditamos na palavra de ninguém, gostamos de testar tudo sozinhos. Portanto, um pré-requisito de nossa concorrência era passar nos testes de estresse. Formulamos tarefas de teste para o carregamento, e três em cada seis empresas concordaram, às suas próprias custas, em implementar um protótipo da solução baseada em tecnologias de memória para testá-lo.

Não vou contar como testamos tudo e quanto tempo levou, resumirei apenas: o melhor desempenho nos testes de carga foi mostrado pelo protótipo da solução baseada em Tarantool da equipe de desenvolvimento do Mail.ru Group. Assinamos um contrato e iniciamos o desenvolvimento. Quatro pessoas eram do Mail.ru Group e do Alfa-Bank havia três desenvolvedores, três analistas de sistemas, um arquiteto de soluções, um proprietário de produto e um Scrum master.

A seguir, falarei sobre como nosso sistema cresceu, como evoluiu, o que fizemos e por que.

Desenvolvimento


Primeiro de tudo, nos perguntamos como obter dados de nossos sistemas atuais. Decidimos que o HTTP é bastante adequado para nós, porque todos os sistemas atuais se comunicam, enviando XML ou JSON por HTTP.

Usamos o servidor HTTP Tarantool interno, porque não precisamos encerrar as sessões SSL, e seu desempenho é suficiente para nós.

Como eu já disse, temos todos os sistemas vivendo em diferentes modelos de dados e, na entrada, precisamos trazer o objeto para o modelo que descreveremos em casa. Era necessária uma linguagem para transformar dados. Escolhemos o imperativo Lua. Executamos todo o código para conversão de dados na sandbox - este é um local seguro, além do qual o código em execução não vai além. Para fazer isso, basta executar uma sequência de caracteres do código necessário, criando um ambiente com funções que não podem bloquear nada ou eliminar algo.


Após a conversão, os dados devem ser verificados quanto à conformidade com o modelo que estamos criando. Discutimos por muito tempo o que um modelo deveria ser, que linguagem usar para descrevê-lo. Paramos no Apache Avro, porque o idioma é simples e conta com o Tarantool. Novas versões do modelo e do código do usuário podem ser colocadas em operação várias vezes ao dia, mesmo sob carga, mesmo sem, a qualquer hora do dia, e se adaptando rapidamente às mudanças.


Após a verificação, os dados devem ser salvos. Fazemos isso com vshard (temos réplicas de shards com espaçamento geográfico).


Além disso, as especificidades são tais que, para a maioria dos sistemas que nos enviam dados, não importa se os recebemos ou não. Portanto, desde o início, implementamos a linha de reparo. O que é isso? Se, por algum motivo, o objeto não passou na transformação ou verificação dos dados, ainda confirmamos o recebimento, mas, ao mesmo tempo, salvamos o objeto na fila de reparo. É consistente, localizado no repositório principal com dados corporativos. Escrevemos imediatamente uma interface de administração para ele, várias métricas e alertas. Como resultado, não perdemos dados. Mesmo se algo mudou na fonte, se o modelo de dados mudou, nós o encontraremos imediatamente e poderemos nos adaptar.


Agora você precisa aprender como recuperar dados armazenados. Analisamos cuidadosamente nossos sistemas e vimos que na pilha clássica de Java e Oracle sempre existe algum tipo de ORM que converte dados de uma visão relacional para uma de objeto. Então, por que não dar objetos imediatamente aos sistemas na forma de um gráfico? Portanto, aceitamos com prazer o GraphQL, que atendeu a todas as nossas necessidades. Ele permite que você receba dados na forma de gráficos e retire apenas o que precisa no momento. Você pode até versão da API com flexibilidade suficiente.


Quase imediatamente, percebemos que os dados extraídos não eram suficientes para nós. Criamos funções que podem ser anexadas a objetos no modelo - na verdade, campos calculados. Ou seja, anexamos uma determinada função ao campo, que, por exemplo, considera o preço médio de uma cotação. E o consumidor externo que solicita os dados nem sabe que esse campo é calculado.


Implementou um sistema de autenticação.


Então eles perceberam que vários papéis se cristalizaram em nossa solução. Uma função é um tipo de agregador de funções. Como regra, as funções têm perfis diferentes de uso de equipamentos:

  • T-Connect: lida com conexões de entrada, limitadas pelo processador, consome pouca memória, não armazena estado.
  • IB-Core: transforma os dados que recebe pelo protocolo Tarantool, ou seja, opera com tablets. Também não armazena estado e pode ser dimensionado.
  • Armazenamento: salva apenas dados, não usa lógica. As interfaces mais simples são implementadas nesta função. Escalável graças a vshard.


Ou seja, com a ajuda de funções, desamarramos diferentes partes do cluster que podem ser dimensionadas independentemente uma da outra.

Portanto, criamos um registro assíncrono de um fluxo de dados transacional e uma fila de reparo com uma interface de administrador. A gravação é assíncrona do ponto de vista comercial: se for garantido o registro de dados para nós mesmos, não importa onde, confirmaremos isso. Se não confirmado, algo deu errado, os dados precisam ser enviados. Esta é uma gravação assíncrona.

Teste


Desde o início do projeto, foi decidido que tentaríamos instilar o desenvolvimento orientado a testes. Nós escrevemos testes de unidade em Lua usando a estrutura tarantool / tap, testes de integração em Python usando a estrutura pytest. Ao mesmo tempo, desenvolvedores e analistas estão envolvidos na criação de testes de integração.

Como aplicamos o desenvolvimento orientado a testes?

Se queremos um novo recurso, primeiro tentamos escrever um teste para ele. Tendo descoberto o bug, devemos primeiro escrever no teste e somente depois corrigi-lo. No início, é difícil trabalhar assim, há um mal-entendido por parte dos funcionários e até sabotagem: "Vamos consertar rapidamente agora, fazer algo novo e cobri-lo com testes". Somente isso "mais tarde" quase nunca ocorre.

Portanto, você deve primeiro se forçar a escrever testes, pedir aos outros que o façam. Acredite, o desenvolvimento orientado a testes é benéfico, mesmo a curto prazo. Você sentirá que ficou mais fácil para você viver. De acordo com nossos sentimentos, 99% do código é coberto por testes agora. Parece muito, mas não temos problemas: os testes são executados em todas as confirmações.

No entanto, acima de tudo, amamos o teste de estresse, o consideramos o mais importante e o conduzimos regularmente.

Vou contar uma pequena história sobre como realizamos o primeiro estágio do teste de carga de uma das primeiras versões. Colocamos o sistema no laptop do desenvolvedor, ligamos a carga e recebemos 4 mil transações por segundo. Bom resultado para um laptop. Colocamos um suporte de carga virtual de quatro servidores, mais fraco que na produção. Implantado no mínimo. Começamos e obtemos um resultado pior do que em um laptop em um thread. Conteúdo de choque.

Ficamos muito tristes. Observamos a carga do servidor e eles acabam ociosos.


Chamamos os desenvolvedores, e eles nos explicam, pessoas que vieram do mundo Java, que o Tarantool é único. Pode ser efetivamente usado por apenas um núcleo do processador sob carga. Em seguida, implantamos o número máximo possível de instâncias do Tarantool em cada servidor, ativamos a carga e já recebemos 14,5 mil transações por segundo.


Eu vou explicar de novo. Devido à divisão em funções que usam recursos de maneira diferente, nossas funções responsáveis ​​pelo processamento de conexões e transformação de dados carregavam apenas o processador e eram estritamente proporcionais à carga.



Além disso, a memória era usada apenas para processar conexões de entrada e objetos temporários.


Pelo contrário, nos servidores de armazenamento, a carga do processador aumentou, mas muito mais lentamente do que nos servidores que lidam com conexões.


E o consumo de memória cresceu em proporção direta à quantidade carregada de dados.


Serviços


Para desenvolver nosso novo produto especificamente como plataforma de aplicativos, criamos um componente para implantar serviços e bibliotecas nele.

Os serviços não são apenas pequenos pedaços de código que operam em alguns campos. Eles podem ser projetos grandes e complexos que fazem parte do cluster, verificam os dados de referência, alteram a lógica de negócios e fornecem respostas. Também exportamos o esquema de serviço para o GraphQL, e o consumidor recebe um ponto de acesso universal a dados, com introspecção em todo o modelo. É muito confortável.

Como os serviços contêm muito mais funções, decidimos que deveria haver bibliotecas nas quais removeríamos o código usado com freqüência. Nós os adicionamos a um ambiente seguro, depois de verificar se isso não quebra nada para nós. E agora podemos definir funções para ambientes adicionais na forma de bibliotecas.

Queríamos que tivéssemos uma plataforma não apenas para armazenamento, mas também para computação. E como já tínhamos várias réplicas e shards, implementamos uma aparência de computação distribuída e a denominamos redução de mapa, porque era como a redução de mapa original.

Sistemas antigos


Nem todos os nossos sistemas antigos podem nos ligar via HTTP e usar o GraphQL, embora eles suportem esse protocolo. Portanto, criamos um mecanismo para replicar dados para esses sistemas.


Se algo mudar para nós, gatilhos peculiares funcionarão na função Armazenamento e a mensagem com as alterações cairá na fila de processamento. É enviado para um sistema externo usando uma função de replicador separada. Esta função não armazena estado.

Novas melhorias


Como você se lembra, de uma perspectiva comercial, fizemos gravações assíncronas. Mas então eles perceberam que não seria suficiente, porque há uma classe de sistemas que precisa receber imediatamente uma resposta sobre o status da operação. Portanto, expandimos nosso GraphQL e adicionamos mutações. Eles se encaixam organicamente no paradigma existente de trabalhar com dados. Temos um único ponto de leitura e gravação para outra classe de sistemas.


Também percebemos que os serviços por si só não seriam suficientes para nós, porque há relatórios bastante pesados ​​que precisam ser criados uma vez por dia, por semana, por mês. Isso pode levar muito tempo e os relatórios podem até bloquear o loop de eventos do Tarantool. Portanto, criamos funções separadas: agendador e corredor. Corredores não armazenam estado. Eles lançam tarefas difíceis, que não podemos contar rapidamente. E a função do planejador monitora o planejamento para iniciar essas tarefas, descrito na configuração. As próprias tarefas são armazenadas no mesmo local que os dados corporativos. Quando chega a hora certa, o agendador pega a tarefa, entrega a algum corredor, ele considera e salva o resultado.


Nem todas as tarefas precisam ser executadas dentro do cronograma. Alguns relatórios precisam ser lidos sob demanda. Assim que esse requisito chega, uma tarefa é formada na caixa de areia e enviada ao corredor para execução. Depois de um tempo, o usuário recebe uma resposta assincronamente de que tudo foi calculado, o relatório está pronto.


Inicialmente, aderimos ao paradigma de salvar todos os dados, versionar e não excluí-los. Mas na vida, de tempos em tempos, você ainda precisa excluir algo, principalmente algumas informações brutas ou intermediárias. Com base na expiração, criamos um mecanismo para limpar o armazenamento de dados obsoletos.


Também entendemos que, mais cedo ou mais tarde, ocorrerá uma situação em que não haverá espaço suficiente para armazenar dados na memória, mas, no entanto, os dados deverão ser armazenados. Para esses fins, em breve faremos armazenamento em disco.


Conclusão


Começamos com a tarefa de carregar dados em um único modelo, passamos três meses em seu desenvolvimento. Tínhamos seis sistemas de provedor de dados. Todo o código de transformação em um único modelo é de cerca de 30 mil linhas em Lua. E a maior parte do trabalho ainda está por vir. Às vezes, há uma falta de motivação para as equipes vizinhas, complicando bastante o trabalho das circunstâncias. Se você enfrentar um problema semelhante, o tempo que achar normal para sua implementação será multiplicado por três ou até quatro.

Lembre-se também de que os problemas existentes nos processos de negócios não podem ser resolvidos com a ajuda de um novo DBMS, mesmo que seja muito produtivo. O que eu quero dizer? No início do nosso projeto, criamos uma impressão entre os clientes de que agora vamos trazer um novo banco de dados rápido e ao vivo! Os processos serão mais rápidos, tudo ficará bem. De fato, a tecnologia não resolve os problemas existentes nos processos de negócios, porque os processos de negócios são pessoas. E você precisa trabalhar com pessoas, não com tecnologia.

O desenvolvimento através de testes nos estágios iniciais pode ser doloroso e demorado. Mas o efeito positivo disso será perceptível mesmo a curto prazo, quando você não precisar fazer nada para realizar testes de regressão.

É extremamente importante realizar testes de carga em todas as etapas do desenvolvimento. Quanto mais cedo você notar algum tipo de falha na arquitetura, mais fácil será corrigi-la, economizando muito tempo no futuro.

Não há nada errado com Lua. Qualquer um pode aprender a escrever sobre ele: um desenvolvedor Java, um desenvolvedor JavaScript, um desenvolvedor Python, um front-end ou back-end. Temos até analistas escrevendo sobre isso.

Quando falamos sobre o fato de não termos SQL, isso aterroriza as pessoas. "Como você obtém dados sem SQL?" Isso é possível? " Claro. Em um sistema de classe OLTP, o SQL não é necessário. Existe uma alternativa na forma de um idioma que retorna a visão orientada a documentos imediatamente. Por exemplo, GraphQL. E há uma alternativa na forma de computação distribuída.

Se você entender que precisará ser dimensionado, projete sua solução no Tarantool imediatamente, para que ela funcione paralelamente em dezenas de instâncias do Tarantool. Caso contrário, será difícil e doloroso, pois o Tarantool pode usar eficientemente apenas um núcleo do processador.

All Articles