¿Cómo hicimos el núcleo del negocio de inversión de Alfa-Bank basado en Tarantool


Una foto de la película "Nuestro universo secreto: la vida oculta de la célula"

El negocio de inversiones es una de las áreas más difíciles en el mundo bancario, porque no solo hay préstamos, préstamos y depósitos, sino también valores, divisas, bienes, derivados y Todo tipo de dificultades en forma de productos estructurales.

Recientemente, hemos visto un aumento en la educación financiera de la población. Cada vez más personas participan en el comercio en los mercados de valores. Las cuentas de inversión individuales aparecieron no hace mucho tiempo. Le permiten operar en los mercados de valores y, al mismo tiempo, recibir deducciones fiscales o no pagar impuestos. Y todos los clientes que acuden a nosotros desean administrar su cartera y ver informes en tiempo real. Además, la mayoría de las veces este portafolio es multiproducto, es decir, las personas son clientes de varias líneas de negocios.

Además, los requisitos de los reguladores, tanto rusos como extranjeros, están creciendo.

Para satisfacer las necesidades actuales y sentar las bases para futuras actualizaciones, hemos desarrollado el núcleo del negocio de inversión basado en Tarantool.

Algunas estadísticas. El negocio de inversión de Alfa-Bank ofrece servicios de corretaje para individuos y entidades legales para brindar oportunidades de comercio en varios mercados de valores, servicios de custodia para el almacenamiento de valores, servicios de administración de fideicomisos para individuos con capital privado y de gran capital, servicios de emisión de valores para otras compañías. El negocio de inversión de Alfa-Bank es de más de 3 mil cotizaciones por segundo, que se descargan de varias plataformas de negociación. Durante la jornada laboral, se realizan más de 300 mil transacciones en los mercados en nombre del banco o de sus clientes. En plataformas externas e internas, se ejecutan hasta 5 mil órdenes por segundo. Al mismo tiempo, todos los clientes, tanto internos como externos, desean ver sus posiciones en tiempo real.

Antecedentes


En algún lugar desde principios de la década de 2000, nuestras áreas de negocios de inversión se han desarrollado de manera independiente: negociación de bolsa, servicios de corretaje, negociación de divisas, negociación de valores y otros derivados sin receta. Como resultado, caímos en la trampa de los pozos funcionales. ¿Lo que es? Cada línea de negocio tiene sus propios sistemas que duplican las funciones de cada uno. Cada sistema tiene su propio modelo de datos, aunque operan con los mismos conceptos: transacciones, instrumentos, contrapartes, cotizaciones y más. Y como cada sistema se desarrolló de forma independiente, surgió un zoológico diverso de tecnologías.

Además, la base de código de los sistemas ya está desactualizada, porque algunos productos se originaron a mediados de la década de 1990. Y en algunas áreas, ralentizó el proceso de desarrollo, hubo problemas con la productividad.

Nuevos requisitos de solución


Las empresas se dieron cuenta de que el desarrollo tecnológico es vital para un mayor desarrollo. Nos asignaron las tareas:

  1. Recopile todos los datos comerciales en un único almacenamiento rápido y en un solo modelo de datos.
  2. No debemos perder ni modificar esta información.
  3. Es necesario versionar los datos, porque en cualquier momento el regulador puede solicitar estadísticas de años anteriores.
  4. No solo debemos traer algunos DBMS nuevos y modernos, sino crear una plataforma para resolver problemas comerciales.

Además, nuestros arquitectos establecen sus condiciones:

  1. La nueva solución debería ser de clase empresarial, es decir, ya debería haber sido probada en algunas grandes empresas.
  2. El modo de operación de la solución debe ser de misión crítica. Esto significa que debemos estar presentes al mismo tiempo en varios centros de datos y experimentar con tranquilidad la desconexión de un centro de datos.
  3. . , , - . , .
  4. , .

Seguimos el camino estándar: formulamos requisitos y contactamos al departamento de compras. De allí obtuvimos una lista de compañías que, en general, están listas para que hagamos esto. Les contaron a todos sobre la tarea, y seis de ellos recibieron una evaluación de las soluciones.

En el banco no creemos la palabra de nadie, nos encanta probar todo por nuestra cuenta. Por lo tanto, un requisito previo de nuestra licitación era pasar las pruebas de estrés. Formulamos tareas de prueba para la carga, y ya tres de cada seis compañías acordaron a su propio costo implementar un prototipo de la solución basada en tecnologías en memoria para probarlo.

No diré cómo probamos todo y cuánto tiempo llevó, solo resumiré: el prototipo de la solución basada en Tarantool del equipo de desarrollo del Grupo Mail.ru mostró el mejor rendimiento en las pruebas de carga. Firmamos un contrato y comenzamos el desarrollo. Cuatro personas eran del Grupo Mail.ru, y de Alfa-Bank había tres desarrolladores, tres analistas de sistemas, un arquitecto de soluciones, un propietario de productos y un maestro Scrum.

A continuación, hablaré sobre cómo creció nuestro sistema, cómo evolucionó, qué hicimos y por qué.

Desarrollo


En primer lugar, nos preguntamos cómo obtener datos de nuestros sistemas actuales. Decidimos que HTTP es bastante adecuado para nosotros, porque todos los sistemas actuales se comunican entre sí, enviando XML o JSON a través de HTTP.

Utilizamos el servidor HTTP incorporado de Tarantool, porque no necesitamos terminar las sesiones SSL, y su rendimiento es suficiente para nosotros.

Como ya dije, tenemos todos los sistemas viviendo en diferentes modelos de datos, y en la entrada necesitamos llevar el objeto al modelo que describiremos en casa. Se necesitaba un lenguaje para transformar los datos. Elegimos el imperativo Lua. Ejecutamos todo el código para la conversión de datos en el entorno limitado: este es un lugar seguro, más allá del cual el código en ejecución no va más allá. Para hacer esto, simplemente haga una cadena de carga del código necesario, creando un entorno con funciones que no puedan bloquear nada o soltar algo.


Después de la conversión, se debe verificar que los datos cumplan con el modelo que estamos creando. Discutimos durante mucho tiempo qué modelo debería ser, qué lenguaje usar para describirlo. Nos detuvimos en Apache Avro, porque el lenguaje es simple y tiene soporte de Tarantool. Las nuevas versiones del modelo y el código de usuario pueden ponerse en funcionamiento varias veces al día, incluso bajo carga, incluso sin, en cualquier momento del día, y adaptarse muy rápidamente a los cambios.


Después de la verificación, los datos deben guardarse. Hacemos esto con vshard (tenemos réplicas geoespaciadas de fragmentos).


Además, los detalles son tales que para la mayoría de los sistemas que nos envían datos, no importa si los recibimos o no. Por lo tanto, desde el principio implementamos la línea de reparación. ¿Lo que es? Si por alguna razón el objeto no pasó la transformación o verificación de datos, entonces todavía confirmamos la recepción, pero al mismo tiempo guardamos el objeto en la cola de reparación. Es coherente, ubicado en el repositorio principal con datos comerciales. Inmediatamente escribimos una interfaz de administrador para ello, varias métricas y alertas. Como resultado, no perdemos datos. Incluso si algo ha cambiado en la fuente, si el modelo de datos ha cambiado, lo encontraremos inmediatamente y podremos adaptarlo.


Ahora necesita aprender cómo recuperar los datos almacenados. Analizamos cuidadosamente nuestros sistemas y vimos que en la pila clásica de Java y Oracle siempre hay algún tipo de ORM que convierte los datos de una vista relacional a una de objeto. Entonces, ¿por qué no dar inmediatamente objetos a los sistemas en forma de gráfico? Por lo tanto, con mucho gusto tomamos GraphQL, que satisfizo todas nuestras necesidades. Le permite recibir datos en forma de gráficos, para extraer solo lo que necesita en este momento. Incluso puede versionar la API con suficiente flexibilidad.


Casi de inmediato, nos dimos cuenta de que los datos extraídos no eran suficientes para nosotros. Realizamos funciones que se pueden adjuntar a objetos en el modelo, de hecho, campos calculados. Es decir, asignamos una determinada función al campo, que, por ejemplo, considera el precio promedio de una cotización. Y el consumidor externo que solicita los datos ni siquiera sabe que este campo está calculado.


Implementado un sistema de autenticación.


Luego notaron que varios roles cristalizaron en nuestra solución. Un rol es un tipo de agregador de funciones. Como regla, los roles tienen diferentes perfiles de uso de equipos:

  • T-Connect: maneja las conexiones entrantes, limitadas por el procesador, consume poca memoria, no almacena el estado.
  • IB-Core: transforma los datos que recibe a través del protocolo Tarantool, es decir, funciona con tabletas. Tampoco almacena el estado y puede escalarse.
  • Almacenamiento: solo guarda datos, no utiliza ninguna lógica. Las interfaces más simples se implementan en este rol. Escalable gracias a vshard.


Es decir, con la ayuda de roles, nos desligamos unas de otras partes del clúster que se pueden escalar de forma independiente.

Entonces, creamos un registro asincrónico de un flujo de datos transaccionales y una cola de reparación con una interfaz de administrador. La grabación es asíncrona desde el punto de vista comercial: si tenemos la garantía de registrar datos para nosotros mismos, sin importar dónde, lo confirmaremos. Si no se confirma, entonces algo salió mal, los datos deben enviarse. Esta es una grabación asincrónica.

Pruebas


Desde el comienzo del proyecto, se decidió que intentaríamos inculcar el desarrollo basado en pruebas. Escribimos pruebas unitarias en Lua usando el marco tarantool / tap, pruebas de integración en Python usando el marco pytest. Al mismo tiempo, tanto los desarrolladores como los analistas participan en la redacción de pruebas de integración.

¿Cómo aplicamos el desarrollo basado en pruebas?

Si queremos alguna característica nueva, primero tratamos de escribir una prueba para ello. Después de descubrir el error, primero debemos escribir en la prueba, y solo luego corregirlo. Al principio, es difícil trabajar así, hay un malentendido por parte de los empleados, incluso sabotaje: "Arreglemos rápidamente ahora, hagamos algo nuevo y luego cubramos con pruebas". Solo que este "posterior" casi nunca ocurre.

Por lo tanto, primero debe obligarse a escribir pruebas, pedir a otros que lo hagan. Créame, el desarrollo basado en pruebas es beneficioso incluso a corto plazo. Sentirás que te ha resultado más fácil vivir. Según nuestros sentimientos, el 99% del código está cubierto por pruebas ahora. Parece mucho, pero no tenemos problemas: las pruebas se ejecutan en cada confirmación.

Sin embargo, sobre todo nos encantan las pruebas de estrés, lo consideramos el más importante y lo realizamos regularmente.

Te contaré una breve historia sobre cómo realizamos la primera etapa de prueba de carga de una de las primeras versiones. Pusimos el sistema en la computadora portátil del desarrollador, encendimos la carga y recibimos 4 mil transacciones por segundo. Buen resultado para una computadora portátil. Ponemos un soporte de carga virtual de cuatro servidores, más débil que en producción. Implementado al mínimo. Lo iniciamos y obtenemos un resultado peor que en una computadora portátil en un hilo. Contenido de choque.

Estábamos muy tristes. Observamos la carga del servidor y resultan inactivos.


Llamamos a los desarrolladores, y nos explican, personas que han venido del mundo de Java, que Tarantool es de un solo subproceso. Puede ser utilizado efectivamente por un solo núcleo de procesador bajo carga. Luego, desplegamos el número máximo posible de instancias de Tarantool en cada servidor, encendimos la carga y recibimos ya 14.5 mil transacciones por segundo.


Lo explicaré nuevamente. Debido a la división en roles que usan los recursos de manera diferente, nuestros roles que fueron responsables de procesar las conexiones y la transformación de datos solo cargaron el procesador, y fue estrictamente proporcional a la carga.



Además, la memoria se usó solo para procesar conexiones entrantes y objetos temporales.


Por el contrario, en los servidores de almacenamiento, la carga del procesador creció, pero mucho más lentamente que en los servidores que manejan conexiones.


Y el consumo de memoria creció en proporción directa a la cantidad de datos cargados.


Servicios


Para desarrollar nuestro nuevo producto específicamente como una plataforma de aplicación, creamos un componente para implementar servicios y bibliotecas en él.

Los servicios no son solo pequeñas piezas de código que operan en algunos campos. Pueden ser diseños bastante grandes y complejos que forman parte del clúster, verifican los datos de referencia, tuercen la lógica empresarial y dan respuestas. También exportamos el esquema de servicio a GraphQL, y el consumidor recibe un punto de acceso a datos universal, con introspección en todo el modelo. Es muy cómodo.

Dado que los servicios contienen muchas más funciones, decidimos que debería haber bibliotecas en las que eliminaremos el código utilizado con frecuencia. Los agregamos a un entorno seguro, después de comprobar que esto no nos rompe nada. Y ahora podemos establecer funciones para entornos adicionales en forma de bibliotecas.

Queríamos que tuviéramos una plataforma no solo para el almacenamiento, sino también para la informática. Y como ya teníamos un montón de réplicas y fragmentos, implementamos una apariencia de computación distribuida y la llamamos reducción de mapa, porque resultó ser como la reducción de mapa original.

Sistemas antiguos


No todos nuestros sistemas antiguos pueden llamarnos a través de HTTP y usar GraphQL, aunque son compatibles con este protocolo. Por lo tanto, creamos un mecanismo para replicar datos en estos sistemas.


Si algo cambia para nosotros, los desencadenantes peculiares funcionan en el rol de Almacenamiento y el mensaje con los cambios cae en la cola de procesamiento. Se envía a un sistema externo utilizando un rol de replicador separado. Este rol no almacena el estado.

Nuevas mejoras


Como recordará, desde una perspectiva comercial, realizamos una grabación asincrónica. Pero luego se dieron cuenta de que no sería suficiente, porque hay una clase de sistemas que necesitan recibir inmediatamente una respuesta sobre el estado de la operación. Por lo tanto, hemos expandido nuestro GraphQL y agregado mutaciones. Se ajustan orgánicamente al paradigma existente de trabajar con datos. Tenemos un único punto de lectura y escritura para otra clase de sistemas.


También nos dimos cuenta de que los servicios por sí solos no serían suficientes para nosotros, porque hay informes bastante pesados ​​que deben construirse una vez al día, una semana o un mes. Esto puede llevar mucho tiempo e incluso los informes pueden bloquear el bucle de eventos de Tarantool. Por lo tanto, hicimos roles separados: planificador y corredor. Los corredores no almacenan estado. Lanzan tareas difíciles, que no podemos contar sobre la marcha. Y la función de planificador supervisa la planificación para iniciar estas tareas, que se describe en la configuración. Las tareas mismas se almacenan en el mismo lugar que los datos comerciales. Cuando llega el momento adecuado, el planificador toma la tarea, se la da a algún corredor, la considera y guarda el resultado.


No todas las tareas deben ejecutarse según lo programado. Algunos informes deben leerse a pedido. Tan pronto como llega este requisito, se forma una tarea en el sandbox y se envía al corredor para su ejecución. Después de un tiempo, el usuario recibe una respuesta asincrónica de que todo se calculó, el informe está listo.


Inicialmente, nos adherimos al paradigma de guardar todos los datos, versionarlos y no eliminarlos. Pero en la vida, de vez en cuando, todavía tiene que eliminar algo, principalmente información cruda o intermedia. Basado en el vencimiento, creamos un mecanismo para limpiar el almacenamiento de datos obsoletos.


También entendemos que tarde o temprano se producirá una situación en la que no habrá suficiente espacio para almacenar datos en la memoria, pero sin embargo, los datos deben almacenarse. Para estos fines, pronto haremos almacenamiento en disco.


Conclusión


Comenzamos con la tarea de cargar datos en un solo modelo, pasamos tres meses en su desarrollo. Teníamos seis sistemas de proveedores de datos. El código de transformación completo en un solo modelo es de aproximadamente 30 mil líneas en Lua. Y la mayor parte del trabajo está por venir. A veces hay una falta de motivación para los equipos vecinos, lo que complica mucho el trabajo de las circunstancias. Si alguna vez enfrenta un problema similar, entonces el tiempo que cree que es normal para su implementación, multiplique por tres, o incluso cuatro.

Recuerde también que los problemas existentes en los procesos comerciales no pueden resolverse con la ayuda de un nuevo DBMS, incluso si es muy productivo. ¿Lo que quiero decir? Al comienzo de nuestro proyecto, creamos una impresión entre los clientes de que ahora traeremos una nueva base de datos rápida y en vivo. Los procesos irán más rápido, todo estará bien. De hecho, la tecnología no resuelve los problemas que existen en los procesos comerciales, porque los procesos comerciales son personas. Y necesita trabajar con personas, no con tecnología.

El desarrollo a través de pruebas en las etapas iniciales puede ser doloroso y llevar mucho tiempo. Pero el efecto positivo será notable incluso a corto plazo, cuando no necesite hacer nada para realizar pruebas de regresión.

Es extremadamente importante realizar pruebas de carga en todas las etapas de desarrollo. Cuanto antes note algún tipo de falla en la arquitectura, más fácil será solucionarlo, esto le ahorrará mucho tiempo en el futuro.

No hay nada malo con Lua. Cualquiera puede aprender a escribir en él: un desarrollador de Java, un desarrollador de JavaScript, un desarrollador de Python, un front-end o back-end. Incluso tenemos analistas que escriben sobre eso.

Cuando hablamos del hecho de que no tenemos SQL, esto aterroriza a las personas. "¿Cómo se obtienen datos sin SQL?" ¿Es eso posible? " Por supuesto. En un sistema de clase OLTP, no se necesita SQL. Existe una alternativa en forma de lenguaje que le devuelve una vista inmediatamente orientada a documentos. Por ejemplo, GraphQL. Y hay una alternativa en forma de computación distribuida.

Si comprende que necesitará escalar, entonces diseñe su solución en Tarantool, inmediatamente, de modo que pueda funcionar en paralelo en docenas de instancias de Tarantool. Si no lo hace, será difícil y doloroso, ya que Tarantool puede usar eficientemente solo un núcleo de procesador.

All Articles