2021-08-12

La optimización de los servicios financieros

Situación previa del cliente La entidad proveniente del segmento financiero tenía problemas en sus sistemas al final de cada mes, muchas transacciones financieras, pago de salarios, préstamos y servicios, muchos usuarios se conectaban a la aplicación a la vez, produciendo un pico de consumo de recursos y de carga masiva de la aplicación. Esto por su parte hacía que caiga el sitio web, y deje de responder hasta que los usuarios dejen de utilizar la aplicación web durante un tiempo considerable. Las consecuencias de esto era que los usuarios del cliente empezaban a quejarse en redes sociales, afectando la imagen de la empresa. Además, al ser un proceso crítico que no lograba concretarse, los altos mandos del negocio decidieron contratarnos para analizar la situación, entregar sugerencias e implementarlas.

Relevamiento, análisis y propuesta

Primeramente, se analizó el punto de entrada del sistema que vendría a ser un Apache 2.2 sobre CentOS 6, un poco desactualizado para la fecha en que se realizaron los trabajos, la última versión en ese momento era CentOS 7 y Apache 2.4, y hoy día CentOS 8 con Apache 2.4 . Se realizaron varios cambios de tuning de Apache, se contactó con el área de desarrollo e infraestructura para comentarles sobre los cambios y realizar algunas pruebas de estrés en entorno de testing. El Apache, aparte de actuar como balanceador de carga y punto principal de entrada hacia las demás aplicaciones, también actuaba como servidor de aplicaciones de lenguaje Perl, por lo que otra de las recomendaciones fue cambiar el balanceador de carga a Nginx con ModSecurity sobre CentOS 7, y dejar el Apache en la zona segura de la red, bajo la protección de un Firewall de Capa 4 y del ModSecurity Capa 7. También se activaron todas las métricas posibles desde el sistema operativo para analizar el comportamiento de los recursos de hardware durante el tiempo más activo del sistema, instalando el paquete Sysstat y ajustando los parámetros del Cronjob correspondiente para que se ejecute cada un minuto, en lugar de 10 minutos que vendría a ser el valor por defecto para la recolección de métricas.

Además, se habilitaron métricas de concurrencia en el servidor Apache, las cuales nos indicaban la cantidad de conexiones activas en determinado momento, pero en tiempo real, no teníamos visibilidad de la cantidad de peticiones a través del tiempo, por lo que creamos un Script Bash y un Cronjob personalizado para recolectar métricas de concurrencia cada 1 segundo, y así poder analizar el patrón de tráfico. Las métricas recolectadas nos indicaban que al llegar aproximadamente a 200 requests concurrentes el servidor Apache/Perl dejaba de responder. Se tuvo que profundizar en un análisis incluyendo los servicios de los que dependía el servidor Apache/Perl, como por ejemplo un Socket escrito en Lenguaje C, que realizaba llamadas a procedimientos almacenados de una base de datos Oracle. Nos dimos cuenta de que el cuello de botella, una vez que solucionamos una capa superior, bajaba a las capas inferiores de la arquitectura, en este momento el cuello de botella pasaba a ser el Socket, ya que estaba escrito en modo monohilo, monoproceso.

Para este punto inicial, la arquitectura y flujo de llamadas era el siguiente: Internet > Firewall capa 4 > Apache/Perl > Socket C > BD Oracle.

Por lo tanto, nuestra propuesta fue realizar los siguientes cambios:
- Reemplazar el balanceador de carga a Nginx con ModSecurity, por las razones expuestas anteriormente.
- Utilizar tecnología de contenedores, para poder escalar lo necesario durante el pico de utilización de las aplicaciones y el resto del mes eliminar las instancias inactivas para optimizar recursos. La tecnología seleccionada fue OKD 3.11, proyecto comunitario upstream del producto empresarial Red Hat OpenShift Container Platform, posicionado como una de las mejores distribuciones de Kubernetes según Forrester.
- Implementar herramientas de monitoreo de aplicaciones (APM) para analizar el rendimiento y la eficacia de los cambios implementados. La herramienta seleccionada para esto fue Elastic APM.
- Migración de tecnologías de aplicaciones, reingeniería de la aplicación web para modernizar el stack tecnológico y que sea lo más escalable posible. - Stack Inicial: Apache/Perl, Socket C migrado a Java Socket.
- Stack propuesto: Spring Boot sobre Contenedores para backend, React para Frontend.

Implementación: tiempos proyectados, tiempo efectivo de trabajo

En base a la decisión del cliente de llevar adelante los cambios propuestos, se realizó la implementación de la arquitectura propuesta, mientras que el equipo de desarrollo del cliente por su parte realizaba la migración y reingeniería de sus aplicaciones, utilizando una arquitectura de microservicios segmentando aún más los dominios de negocio en aplicaciones individuales. Lo único que no cambió en la ecuación fue la base de datos Oracle, la cual contiene todos los procedimientos almacenados necesarios para procesar la lógica de negocio, pero que tiene la capacidad suficiente para soportar varios miles de requests por segundo. En cuanto a los tiempos proyectados, se previó la implementación en las sgtes etapas:
- Etapa 1: Implementación de Nginx + ModSecurity en alta disponibilidad, activo/pasivo con keepalived. 3 meses
- Etapa 2: Implementación de OKD, 3 Masters, 3 Infra, 3 Workers. Despliegue de aplicaciones, implementación de flujos de CI/CD básicos. 3 meses
- Etapa 3:
Implementación de herramienta de monitoreo Elastic APM, integración con aplicaciones desplegadas sobre OKD. 1 Mes
- Etapa 4:
Realizar pruebas de estrés de aplicación desplegada sobre OKD, en varias iteraciones, quitando métricas de mejoría de la aplicación. 6 meses.

Tiempo estimado: 13 meses

Etapa 1
Se implementó el Nginx con el módulo de seguridad ModSecurity, que, si bien no provee todas las funcionalidades de un WAF como F5, ya nos protege al menos de ataques básicos como algunos tipos de SQL Injection y XSS. Se realizó una implementación con un clúster activo pasivo, utilizando la herramienta keepalived para el efecto, y también se creó un script de migración de configuraciones desde el servidor primario al servidor secundario. Se implementaron 2 ambientes, 1 de Testing y 1 de Producción, de tal manera a que el de Testing nos sirva para poder realizar pruebas de carga, pruebas de tuning y verificación de la validez de los cambios. Se configuraron inicialmente los servicios de balanceo de carga apuntando a la aplicación Apache/Perl de la arquitectura inicial, más adelante lo cambiaríamos apuntando al OKD.
Etapa 2
Se implementó un clúster de OKD versión 3.11 en ese momento ya que la versión 4 todavía estaba en fase de pruebas, con la arquitectura mencionada: 3 masters, 3 nodos de infraestructura y 3 nodos worker de aplicaciones. Se crearon 3 proyectos (kubernetes namespaces) principales para la aplicación a ser migrada, uno por cada ambiente: Desarrollo, Homologación, Producción. Se desplegaron inicialmente las aplicaciones originales, fue un desafío migrar sobre todo la aplicación Perl, ya que tenía mucho código en duro, IPs de servicios web y del socket que consultaba, por ejemplo. Aplicamos las siguientes mejoras a la aplicación Perl:
- Cambiar IPs por nombres DNS - Obtener todos los datos a partir de variables de entorno que podíamos configurar en OKD una vez desplegada la aplicación
- Cambiar todas las URL absolutas por URLs relativas siempre que fuera posible.
También para esta misma aplicación tuvimos que crear una imagen base de Source to Image personalizada, basada en la imagen base original de la comunidad de OKD.
En cuanto a la aplicación Socket C, se migró su funcionalidad completamente a Java SE Estándar, utilizando solamente como dependencia la librería Hikari para administración del pool de conexiones a la Base de Datos Oracle, y el Driver JDBC de Oracle. Esta implementación de la aplicación Socket se realizó en un tiempo record de 24 horas seguidas para la versión inicial, casi sin descansar, debido a que llegaba en ese momento el deadline para la activación del proceso mensual. Una vez migrada la aplicación a java, se utilizó la imagen base de OpenJDK 11 de source-to-image para realizar el despliegue de la misma a OKD, obteniendo así inmediatamente las capacidades de escalamiento horizontal automático que ofrece la plataforma. Una vez que el equipo de desarrollo terminó la creación de las aplicaciones sobre Spring Boot y React, también se desplegaron alrededor de 20 microservicios nuevos sobre la plataforma, manteniendo la separación de ambientes. Otras herramientas auxiliares utilizadas:
- Nexus: repositorio de dependencias Java y de Imágenes base de Contenedores.
- Gitlab: repositorio de código fuente, versionado, implementación de webhooks para que cuando el desarrollador realice push de código a la rama designada, se inicie un build de la imagen de contenedor de la aplicación en cuestión en OKD, además del despliegue de la aplicación.
- Keycloak: servidor de Single Sign-On, para la integración de autenticación con Active Directory, en principio utilizado para autenticar OKD, sin embargo, en los últimos tiempos también se está pensando en migrar la autenticación de aplicaciones a este servicio.
Etapa 3
Se realizó el despliegue de un modo de servidor Elastic APM, para empezar a analizar las métricas de rendimiento de las aplicaciones, se realizó la integración del agente agregándolo a la imagen base de las aplicaciones Java con Spring Boot, y apuntando al servidor correspondiente utilizando variables de entorno en OKD Las métricas permitieron revelar al final de la Etapa 4 que la Base de Datos de desarrollo llegaba hasta 400 requests concurrentes, antes de empezar a generar bloqueos a nivel de tablas cada vez más grandes y lentos.
Etapa 4
La mayor parte del tiempo del cronograma fue en esta etapa, desde la generación del caso de prueba con Jmeter, hasta la ejecución y posterior análisis de los resultados, en varias iteraciones, mejorando un poco más en cada iteración. La primera parte de esta etapa consiste en la creación del caso de prueba de Jmeter, comenzando por utilizar la funcionalidad de Requests Recorder que tiene, levantando un servidor proxy local y capturando los requests de la aplicación para poder empezar a trabajar sobre eso. Luego, limpiar los requests innecesarios, utilizar variables para tomar los datos dinámicos de la aplicación, tales como headers y parámetros pasados entre request y request, utilizar expresiones regulares para capturar información, y tomar datos de un archivo CSV con datos ficticios de usuarios para hacer más natural la prueba.

Con el caso de prueba creado, se empezó a tomar una medida base de los tiempos de respuesta y la máxima concurrencia soportada por la aplicación, antes de que los tiempos de respuesta se empiecen a disparar. Con esto se tiene una base de cuánto soporta la aplicación, para saber si los posteriores cambios realizados son beneficiosos o no. Luego de las primeras pruebas de estrés, se fue modificando y tuneando configuraciones de los Nginx, del OKD, y de las aplicaciones. Una de esas modificaciones fue a nivel de arquitectura y consistió en reemplazar la base de datos por una cache en memoria llamada Redis, para los datos de sesión del usuario. Resultados. Comportamiento de la solución implementada vs la propuesta técnica Al final de la Etapa 4, las últimas métricas nos mostraron un entorno de testing que llegábamos a duplicar la cantidad de requests/segundo original, pasamos de 200 requests por segundo a 400 requests por segundo, que se traducen en 12.000 RPM (requests por minuto) y 24.000 RPM respectivamente.

Esto se logró gracias a la rapidez con la que atienden las peticiones los servidores Nginx, además de la gran escalabilidad que nos brinda la plataforma OKD, capaz de levantar instancias nuevas de las aplicaciones en segundos de ser necesario, a partir de la información de consumo de CPU que obtiene del subsistema de monitoreo y métricas. Además de crecer en cuanto a capacidad de concurrencia de usuarios, este cliente logró automatizar todo su proceso de construcción de imágenes de contenedores y despliegue de sus aplicaciones de tal manera que con el “push” de código al repositorio de código fuente, se podría desplegar a cualquier ambiente en minutos, en lugar de horas. Este cliente hoy día despliega alrededor de 100 veces por día en entornos pre-productivos y al menos 2 veces a la semana en entornos productivos, mientras que anteriormente sacaba cambios nuevos en producción cada cierta cantidad de meses. Aun así, el cliente tiene la posibilidad de realizar el despliegue a producción sin caída de servicio en el horario que más le convenga, aún en tiempos con mucha concurrencia.

Conclusiones

Todo el proceso realizado es uno de mejora continua, por lo que seguimos trabajando en mejorar la concurrencia, los tiempos de respuesta, la arquitectura de aplicaciones, el tuning de base de datos con los DBA, etc. Por lo tanto, queda camino por recorrer, algunas ideas que nos gustaría abordar a futuro son, por ejemplo:
- Utilizar la compilación a código nativo del Framework Quarkus
- Migración de CentOS 7 a CentOS Stream 8
- Migración de OKD 3.11 a OKD 4.8, entre otros

Contáctenos
Whatsapp
Mail