La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Esta es la continuación de una larga historia sobre nuestro espinoso camino hacia la creación de un sistema potente y de alta carga que garantice el funcionamiento de Exchange. La primera parte está aquí: habr.com/es/post/444300

error misterioso

Después de numerosas pruebas, se puso en funcionamiento el sistema actualizado de comercio y compensación y encontramos un error sobre el cual pudimos escribir una historia detectivesca y mística.

Poco después del lanzamiento en el servidor principal, una de las transacciones se procesó con un error. Sin embargo, todo estuvo bien en el servidor de respaldo. ¡Resultó que una simple operación matemática de calcular el exponente en el servidor principal dio un resultado negativo del argumento real! Continuamos nuestra investigación y en el registro SSE2 encontramos una diferencia de un bit, que es responsable del redondeo cuando se trabaja con números de coma flotante.

Escribimos una utilidad de prueba simple para calcular el exponente con el bit de redondeo configurado. Resultó que en la versión de RedHat Linux que usamos, había un error al trabajar con la función matemática cuando se insertaba el bit desafortunado. Informamos esto a RedHat, después de un tiempo recibimos un parche de ellos y lo implementamos. El error ya no se produjo, pero no estaba claro de dónde procedía este bit. La función fue responsable de ello. fesetround del lenguaje C. Analizamos cuidadosamente nuestro código en busca del supuesto error: comprobamos todas las situaciones posibles; analizó todas las funciones que utilizaban redondeo; intentó reproducir una sesión fallida; usé diferentes compiladores con diferentes opciones; Se utilizaron análisis estático y dinámico.

No se pudo encontrar la causa del error.

Luego comenzaron a comprobar el hardware: realizaron pruebas de carga de los procesadores; revisó la RAM; Incluso realizamos pruebas para el escenario muy improbable de un error de varios bits en una celda. En vano.

Al final, nos decidimos por una teoría del mundo de la física de altas energías: una partícula de alta energía voló hacia nuestro centro de datos, atravesó la pared de la carcasa, golpeó el procesador y provocó que el pestillo del gatillo se atascara en ese mismo lugar. Esta absurda teoría se llamó "neutrino". Si está lejos de la física de partículas: los neutrinos casi no interactúan con el mundo exterior y ciertamente no pueden afectar el funcionamiento del procesador.

Como no fue posible encontrar la causa del fallo, el servidor "infractor" fue retirado del funcionamiento por si acaso.

Después de un tiempo, comenzamos a mejorar el sistema de respaldo en caliente: introdujimos las llamadas "reservas en caliente" (cálidas), réplicas asíncronas. Recibieron un flujo de transacciones que podrían ubicarse en diferentes centros de datos, pero los Warms no interactuaron activamente con otros servidores.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

¿Por qué se hizo esto? Si el servidor de respaldo falla, el enlace activo al servidor principal se convierte en el nuevo respaldo. Es decir, después de un fallo, el sistema no permanece en un servidor principal hasta el final de la sesión de negociación.

Y cuando se probó y puso en funcionamiento la nueva versión del sistema, volvió a ocurrir el error de bit de redondeo. Además, con el aumento en el número de servidores calientes, el error comenzó a aparecer con más frecuencia. Al mismo tiempo, el vendedor no tenía nada que demostrar, ya que no había pruebas concretas.

Durante el siguiente análisis de la situación, surgió la teoría de que el problema podría estar relacionado con el sistema operativo. Escribimos un programa simple que llama a una función en un bucle sin fin. fesetround, recuerda el estado actual y lo verifica durante el sueño, y esto se hace en muchos hilos de la competencia. Después de seleccionar los parámetros de suspensión y la cantidad de subprocesos, comenzamos a reproducir consistentemente la falla del bit después de aproximadamente 5 minutos de ejecutar la utilidad. Sin embargo, el soporte de Red Hat no pudo reproducirlo. Las pruebas de nuestros otros servidores han demostrado que sólo aquellos con ciertos procesadores son susceptibles al error. Al mismo tiempo, cambiar a un nuevo kernel resolvió el problema. Al final, simplemente reemplazamos el sistema operativo y la verdadera causa del error no quedó clara.

Y de repente el año pasado se publicó un artículo sobre Habré “Cómo encontré un error en los procesadores Intel Skylake" La situación descrita en él era muy similar a la nuestra, pero el autor llevó la investigación más allá y propuso la teoría de que el error estaba en el microcódigo. Y cuando se actualizan los kernels de Linux, los fabricantes también actualizan el microcódigo.

Mayor desarrollo del sistema.

Aunque nos deshicimos del error, esta historia nos obligó a reconsiderar la arquitectura del sistema. Después de todo, no estábamos protegidos contra la repetición de tales errores.

Los siguientes principios formaron la base para las próximas mejoras del sistema de reservas:

  • No puedes confiar en nadie. Es posible que los servidores no funcionen correctamente.
  • Reserva mayoritaria.
  • Garantizar el consenso. Como complemento lógico a la reserva mayoritaria.
  • Son posibles dobles fracasos.
  • Vitalidad. El nuevo sistema Hot Standby no debería ser peor que el anterior. El comercio debe realizarse ininterrumpidamente hasta el último servidor.
  • Ligero aumento de la latencia. Cualquier tiempo de inactividad conlleva enormes pérdidas financieras.
  • Interacción de red mínima para mantener la latencia lo más baja posible.
  • Seleccionar un nuevo servidor maestro en segundos.

Ninguna de las soluciones disponibles en el mercado nos convenía y el protocolo Raft aún estaba en su infancia, por lo que creamos nuestra propia solución.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Redes

Además del sistema de reservas, comenzamos a modernizar la interacción en la red. El subsistema de E/S constaba de muchos procesos, que tenían el peor impacto en la fluctuación y la latencia. Con cientos de procesos que manejan conexiones TCP, nos vimos obligados a cambiar constantemente entre ellos y, en una escala de microsegundos, esta es una operación que requiere bastante tiempo. Pero la peor parte es que cuando un proceso recibió un paquete para procesarlo, lo envió a una cola de SystemV y luego esperó un evento de otra cola de SystemV. Sin embargo, cuando hay una gran cantidad de nodos, la llegada de un nuevo paquete TCP en un proceso y la recepción de datos en la cola en otro representan dos eventos en competencia para el sistema operativo. En este caso, si no hay procesadores físicos disponibles para ambas tareas, se procesará una y la segunda se colocará en cola de espera. Es imposible predecir las consecuencias.

En tales situaciones, se puede utilizar el control dinámico de prioridad de procesos, pero esto requerirá el uso de llamadas al sistema que consumen muchos recursos. Como resultado, cambiamos a un hilo usando epoll clásico, lo que aumentó considerablemente la velocidad y redujo el tiempo de procesamiento de la transacción. También nos deshicimos de los procesos de comunicación de red separados y de la comunicación a través de SystemV, redujimos significativamente la cantidad de llamadas al sistema y comenzamos a controlar las prioridades de las operaciones. Sólo en el subsistema de E/S, fue posible ahorrar entre 8 y 17 microsegundos, según el escenario. Este esquema de subproceso único se ha utilizado sin cambios desde entonces; un subproceso epoll con un margen es suficiente para dar servicio a todas las conexiones.

Procesamiento de transacciones

La creciente carga de nuestro sistema requirió actualizar casi todos sus componentes. Pero, lamentablemente, el estancamiento en el crecimiento de las velocidades de reloj de los procesadores en los últimos años ya no ha permitido escalar los procesos de frente. Por lo tanto, decidimos dividir el proceso del Motor en tres niveles, siendo el más activo el sistema de verificación de riesgos, que evalúa la disponibilidad de fondos en las cuentas y crea las transacciones en sí. Pero el dinero puede estar en diferentes monedas y era necesario determinar sobre qué base dividir el procesamiento de las solicitudes.

La solución lógica es dividirlo por moneda: un servidor comercia en dólares, otro en libras y un tercero en euros. Pero si con este esquema se envían dos transacciones para comprar monedas diferentes, entonces surgirá el problema de la desincronización de la billetera. Pero la sincronización es difícil y costosa. Por lo tanto, sería correcto fragmentar por separado por billeteras y por separado por instrumentos. Por cierto, la mayoría de las bolsas occidentales no tienen la tarea de comprobar los riesgos con tanta precisión como nosotros, por lo que la mayoría de las veces esto se hace fuera de línea. Necesitábamos implementar la verificación en línea.

Expliquemos con un ejemplo. Un comerciante quiere comprar $30 y la solicitud pasa a la validación de la transacción: verificamos si este comerciante tiene permiso para este modo de negociación y si tiene los derechos necesarios. Si todo está en orden, la solicitud pasa al sistema de verificación de riesgos, es decir comprobar la suficiencia de fondos para concluir una transacción. Hay una nota que indica que la cantidad requerida está actualmente bloqueada. Luego, la solicitud se envía al sistema comercial, que aprueba o desaprueba la transacción. Digamos que se aprueba la transacción; luego el sistema de verificación de riesgos marca que el dinero está desbloqueado y los rublos se convierten en dólares.

En general, el sistema de verificación de riesgos contiene algoritmos complejos y realiza una gran cantidad de cálculos que requieren muchos recursos, y no se limita a verificar el "saldo de la cuenta", como podría parecer a primera vista.

Cuando comenzamos a dividir el proceso del motor en niveles, nos encontramos con un problema: el código disponible en ese momento usaba activamente la misma matriz de datos en las etapas de validación y verificación, lo que requería reescribir toda la base del código. Como resultado, tomamos prestada una técnica para procesar instrucciones de los procesadores modernos: cada una de ellas se divide en pequeñas etapas y se realizan varias acciones en paralelo en un ciclo.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Después de una pequeña adaptación del código, creamos un pipeline para el procesamiento de transacciones en paralelo, en el que la transacción se dividió en 4 etapas del pipeline: interacción de red, validación, ejecución y publicación del resultado.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Veamos un ejemplo. Disponemos de dos sistemas de procesamiento, serie y paralelo. Llega la primera transacción y se envía para su validación en ambos sistemas. La segunda transacción llega inmediatamente: en un sistema paralelo se pone inmediatamente a trabajar, y en un sistema secuencial se pone en cola esperando que la primera transacción pase por la etapa de procesamiento actual. Es decir, la principal ventaja del procesamiento de canalizaciones es que procesamos la cola de transacciones más rápido.

Así surgió el sistema ASTS+.

Es cierto que tampoco todo es tan sencillo con los transportadores. Digamos que tenemos una transacción que afecta matrices de datos en una transacción vecina; esta es una situación típica para un intercambio. Una transacción de este tipo no se puede ejecutar en un proceso porque puede afectar a otros. Esta situación se denomina riesgo de datos y dichas transacciones simplemente se procesan por separado: cuando se agotan las transacciones "rápidas" en la cola, la canalización se detiene, el sistema procesa la transacción "lenta" y luego inicia la canalización nuevamente. Afortunadamente, la proporción de este tipo de transacciones en el flujo general es muy pequeña, por lo que el proceso se detiene tan raramente que no afecta el rendimiento general.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Luego comenzamos a resolver el problema de sincronizar tres hilos de ejecución. El resultado fue un sistema basado en un buffer circular con celdas de tamaño fijo. En este sistema todo está sujeto a la velocidad de procesamiento, los datos no se copian.

  • Todos los paquetes de red entrantes entran en la etapa de asignación.
  • Los colocamos en una matriz y los marcamos como disponibles para la etapa n.° 1.
  • Llegó la segunda transacción, nuevamente está disponible para la etapa N°1.
  • El primer subproceso de procesamiento ve las transacciones disponibles, las procesa y las mueve a la siguiente etapa del segundo subproceso de procesamiento.
  • Luego procesa la primera transacción y marca la celda correspondiente. deleted — ahora está disponible para un nuevo uso.

Toda la cola se procesa de esta manera.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

El procesamiento de cada etapa lleva unidades o decenas de microsegundos. Y si utilizamos esquemas de sincronización del sistema operativo estándar, perderemos más tiempo en la sincronización misma. Por eso empezamos a usar spinlock. Sin embargo, esto es muy malo en un sistema en tiempo real, y RedHat no recomienda estrictamente hacer esto, por lo que aplicamos un bloqueo de giro durante 100 ms y luego cambiamos al modo de semáforo para eliminar la posibilidad de un punto muerto.

Como resultado, logramos un rendimiento de aproximadamente 8 millones de transacciones por segundo. Y literalmente dos meses después en статье Sobre LMAX Disruptor vimos una descripción de un circuito con la misma funcionalidad.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Ahora podría haber varios hilos de ejecución en una etapa. Todas las transacciones se procesaron una por una, en el orden en que fueron recibidas. Como resultado, el rendimiento máximo aumentó de 18 mil a 50 mil transacciones por segundo.

Sistema de gestión de riesgos cambiarios.

La perfección no tiene límites y pronto comenzamos nuevamente la modernización: en el marco de ASTS+, comenzamos a trasladar los sistemas de gestión de riesgos y operaciones de liquidación a componentes autónomos. Desarrollamos una arquitectura moderna flexible y un nuevo modelo de riesgo jerárquico, y tratamos de utilizar la clase siempre que fue posible. fixed_point en lugar de double.

Pero inmediatamente surgió un problema: ¿cómo sincronizar toda la lógica de negocios que ha estado funcionando durante muchos años y transferirla al nuevo sistema? Como resultado, hubo que abandonar la primera versión del prototipo del nuevo sistema. La segunda versión, que actualmente se encuentra en producción, se basa en el mismo código, que funciona tanto en la parte comercial como en la de riesgo. Durante el desarrollo, lo más difícil fue fusionar dos versiones. Nuestro colega Evgeniy Mazurenok realizaba esta operación todas las semanas y cada vez maldecía durante mucho tiempo.

Al seleccionar un nuevo sistema, inmediatamente tuvimos que resolver el problema de la interacción. Al elegir un bus de datos, era necesario garantizar una fluctuación estable y una latencia mínima. La red InfiniBand RDMA era la más adecuada para esto: el tiempo de procesamiento promedio es 4 veces menor que en las redes Ethernet 10 G. Pero lo que realmente nos cautivó fue la diferencia de percentiles: 99 y 99,9.

Por supuesto, InfiniBand tiene sus desafíos. En primer lugar, una API diferente: ibverbs en lugar de sockets. En segundo lugar, casi no existen soluciones de mensajería de código abierto ampliamente disponibles. Intentamos crear nuestro propio prototipo, pero resultó muy difícil, por lo que elegimos una solución comercial: Confinity Low Latency Messaging (anteriormente IBM MQ LLM).

Entonces surgió la tarea de dividir adecuadamente el sistema de riesgos. Si simplemente elimina el motor de riesgo y no crea un nodo intermedio, entonces se pueden mezclar transacciones de dos fuentes.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Las soluciones llamadas de latencia ultrabaja tienen un modo de reordenación: las transacciones de dos fuentes se pueden organizar en el orden requerido al recibirlas; esto se implementa utilizando un canal separado para intercambiar información sobre la orden. Pero todavía no utilizamos este modo: complica todo el proceso y en varias soluciones no es compatible en absoluto. Además, a cada transacción se le tendrían que asignar marcas de tiempo correspondientes, y en nuestro esquema este mecanismo es muy difícil de implementar correctamente. Por tanto, utilizamos el esquema clásico con un broker de mensajes, es decir, con un despachador que distribuye mensajes entre Risk Engine.

El segundo problema estaba relacionado con el acceso del cliente: si hay varios Risk Gateways, el cliente necesita conectarse a cada uno de ellos, y esto requerirá cambios en la capa del cliente. Queríamos alejarnos de esto en esta etapa, por lo que el diseño actual de Risk Gateway procesa todo el flujo de datos. Esto limita en gran medida el rendimiento máximo, pero simplifica enormemente la integración del sistema.

duplicación

Nuestro sistema no debe tener un único punto de fallo, es decir, todos los componentes deben estar duplicados, incluido el broker de mensajes. Resolvimos este problema utilizando el sistema CLLM: contiene un clúster RCMS en el que dos despachadores pueden trabajar en modo maestro-esclavo, y cuando uno falla, el sistema cambia automáticamente al otro.

Trabajar con un centro de datos de respaldo

InfiniBand está optimizado para funcionar como una red local, es decir, para conectar equipos de montaje en bastidor, y no se puede instalar una red InfiniBand entre dos centros de datos distribuidos geográficamente. Por lo tanto, implementamos un puente/despachador, que se conecta al almacenamiento de mensajes a través de redes Ethernet regulares y transmite todas las transacciones a una segunda red IB. Cuando necesitamos migrar desde un centro de datos, podemos elegir con qué centro de datos trabajar ahora.

resultados

Todo lo anterior no se hizo de una vez; fueron necesarias varias iteraciones para desarrollar una nueva arquitectura. Creamos el prototipo en un mes, pero nos llevó más de dos años ponerlo en condiciones de funcionar. Intentamos lograr el mejor compromiso entre aumentar el tiempo de procesamiento de transacciones y aumentar la confiabilidad del sistema.

Dado que el sistema se actualizó en gran medida, implementamos la recuperación de datos de dos fuentes independientes. Si el almacén de mensajes no funciona correctamente por algún motivo, puede tomar el registro de transacciones de una segunda fuente: desde Risk Engine. Este principio se observa en todo el sistema.

Entre otras cosas, pudimos preservar la API del cliente para que ni los corredores ni nadie más necesitaran reelaboraciones significativas para la nueva arquitectura. Tuvimos que cambiar algunas interfaces, pero no fue necesario realizar cambios significativos en el modelo operativo.

A la versión actual de nuestra plataforma la llamamos Rebus, como abreviatura de las dos innovaciones más notables en la arquitectura, Risk Engine y BUS.

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Al principio queríamos asignar sólo la parte de compensación, pero el resultado fue un sistema distribuido enorme. Los clientes ahora pueden interactuar con Trade Gateway, Clearing Gateway o ambos.

Lo que finalmente logramos:

La evolución de la arquitectura del sistema de negociación y compensación de la Bolsa de Moscú. Parte 2

Reducido el nivel de latencia. Con un pequeño volumen de transacciones, el sistema funciona igual que la versión anterior, pero al mismo tiempo puede soportar una carga mucho mayor.

El rendimiento máximo aumentó de 50 mil a 180 mil transacciones por segundo. Un nuevo aumento se ve obstaculizado por el único flujo de igualación de pedidos.

Hay dos formas de seguir mejorando: paralelizar la coincidencia y cambiar la forma en que funciona con Gateway. Ahora todas las puertas de enlace funcionan según un esquema de replicación que, bajo tal carga, deja de funcionar normalmente.

Finalmente, puedo dar algunos consejos a quienes están ultimando sistemas empresariales:

  • Esté preparado para lo peor en todo momento. Los problemas siempre surgen inesperadamente.
  • Generalmente es imposible rehacer rápidamente la arquitectura. Especialmente si necesita lograr la máxima confiabilidad en múltiples indicadores. Cuantos más nodos, más recursos se necesitarán para el soporte.
  • Todas las soluciones personalizadas y patentadas requerirán recursos adicionales para investigación, soporte y mantenimiento.
  • No posponga la resolución de problemas de confiabilidad y recuperación del sistema después de fallas; téngalos en cuenta en la etapa de diseño inicial.

Fuente: habr.com

Añadir un comentario