<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Enrique Esquivel Escalante</title>
    <description>The latest articles on DEV Community by Enrique Esquivel Escalante (@enrique_esquivel).</description>
    <link>https://dev.to/enrique_esquivel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4219776%2Fbf6368c1-9d91-4927-b6b7-d2176cbeeef2.png</url>
      <title>DEV Community: Enrique Esquivel Escalante</title>
      <link>https://dev.to/enrique_esquivel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/enrique_esquivel"/>
    <language>en</language>
    <item>
      <title>¿Todavía es COBOL "el" motor de las aplicaciones financieras?</title>
      <dc:creator>Enrique Esquivel Escalante</dc:creator>
      <pubDate>Sun, 01 Nov 2026 00:51:27 +0000</pubDate>
      <link>https://dev.to/enrique_esquivel/todavia-es-cobol-el-motor-de-las-aplicaciones-financieras-5fnm</link>
      <guid>https://dev.to/enrique_esquivel/todavia-es-cobol-el-motor-de-las-aplicaciones-financieras-5fnm</guid>
      <description>&lt;p&gt;&lt;a href="//dev.to/enrique_esquivel/is-cobol-still-the-engine-behind-financial-applications-3i91"&gt;English version&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este es el primer blog de una serie que va a tratar de lecciones aprendidas durante mi carrera. Actualmente trabajo como manager de ingeniería en Google (&lt;em&gt;Este es un proyecto personal. Las opiniones aquí expresadas son mías y no representan las de mi empleador, Google&lt;/em&gt;), pero fui consultor por más 15 años, colaborando con empresas de diversas industrias. Ahí me encontré con varios retos que en su momento no pude resolver completamente. Muchas veces tuve que entregar soluciones que funcionaban con limitaciones, por falta de tiempo, de tecnología o de personal.&lt;/p&gt;

&lt;p&gt;Ahora con la IA ya no tengo esas limitaciones. Puedo explorar diferentes soluciones, tomar decisiones con base en datos, y me gustaría por fin completar estos problemas y compartir las lecciones aprendidas, aunque al final del día quien termine leyendo esto sea otra IA. &lt;/p&gt;

&lt;h2&gt;
  
  
  "El" motor financiero que nadie debe tocar
&lt;/h2&gt;

&lt;p&gt;Quiero empezar con un problema que me encontré muchas veces. Cuando desarrollábamos funcionalidades nuevas sobre el motor de empresas financieras, sus usuarios me decían lo mismo: no podemos cambiar de motor, no hay nada más que COBOL que soporte el número de transacciones que tenemos.&lt;/p&gt;

&lt;p&gt;Y durante mucho tiempo esto fue cierto. IBM fabrica el hardware de los mainframes, y COBOL corre encima; están muy acoplados, pero son muy eficientes. Para tener una idea, IBM dice que CICS, su sistema de transacciones donde corre COBOL, sumando a todos sus clientes en el mundo, procesa más de 1.1 millones de transacciones por segundo (&lt;a href="https://www.techtarget.com/searchdatacenter/definition/CICS" rel="noopener noreferrer"&gt;TechTarget&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;El problema es que los mainframes son caros, y requieren un pago constante para mantenerlos actualizados. Para una fintech, un banco pequeño o una financiera no es costeable tener un mainframe, entonces terminan corriendo COBOL en servidores x86, y ahí el número de transacciones por segundo baja drásticamente. En una prueba que publicó AWS con Micro Focus en 2022, un servidor de 2 CPUs llegó a unas 400 transacciones por segundo, uno de 16 CPUs a 3,200, y para pasar de 4,000 tuvieron que poner nueve servidores en paralelo (&lt;a href="https://aws.amazon.com/blogs/mt/scaling-for-performance-with-aws-mainframe-modernization-and-micro-focus/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Es ahí cuando empresas que usan COBOL en x86 tienen problemas con su motor financiero, quieren crecer y ya no pueden. Sé lo que estás pensando: se puede escalar agregando nodos y particionando. Sí, COBOL también se escala así; es justo lo que hizo AWS en esa prueba. Pero si de todas formas vas a particionar para llegar a unos cuantos miles de transacciones por segundo, ¿por qué no reemplazarlo? ¿Por qué seguir usándolo, si cada vez hay menos gente experta en COBOL?&lt;/p&gt;

&lt;p&gt;Pues aquí es donde empezó mi investigación.&lt;/p&gt;

&lt;h2&gt;
  
  
  No quería otro Hello World
&lt;/h2&gt;

&lt;p&gt;Para probar un reemplazo de COBOL, Yo quería probar que se puede transaccionar con una arquitectura más realista. No quise hacer un Hello World y nada más presumir el número de transacciones por segundo de un ejemplo muy simple, sino algo que se pareciera a un negocio de verdad.&lt;/p&gt;

&lt;p&gt;Entonces trabajé con una empresa ficticia que administra fondos de pensión. Como sabes, muchas empresas en Latinoamérica trabajan con fondos privados de pensión; en México estos planes privados tienen además algunos beneficios fiscales. Una vez al mes las empresas depositan al fondo, además los empleados también pueden participar con su propio dinero. Cada empleado contribuye diferente, muchas veces dependiendo de su salario. Todo eso se junta, se manda en conjunto a un fondo de inversión de bajo riesgo, y así es como genera rendimientos. Al final de cada día, se calcula cuánto tiene en su cuenta cada empleado de cada organización que participa.&lt;/p&gt;

&lt;p&gt;Este me pareció un caso interesante porque el número de transacciones es alto, sobre todo cuando las empresas mandan las contribuciones a fin de mes. Y se complica todavía más cuando hay que dar de alta un nuevo empleador, porque un fondo así siempre está buscando clientes nuevos, y repentinamente tienes que agregar 10,000 o 15,000 nuevos empleados al fondo.&lt;/p&gt;

&lt;p&gt;Este negocio tiene ciclos diarios y mensuales; para ver resultados hubiéramos tenido que esperar meses. Entonces hice un pequeño hack que me pareció interesante: un reloj financiero que no sigue al reloj de la pared. Yo decido cuándo pasa un día hábil y con un comando avanzo el fondo al siguiente: llegan las contribuciones, se calcula el valor del día y se cierra. Así observamos en minutos lo que en la vida real tomaría semanas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que tuve que aprender, y donde la IA me ayudó
&lt;/h2&gt;

&lt;p&gt;Tengo que aceptar que yo no conocía mucho este tipo de negocio, y la IA me ayudó primero a entenderlo y luego a modelarlo. La pieza clave es algo que se llama NAV, el &lt;em&gt;net asset value&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cada contribución se transforma en un número de unidades. Esas unidades no cambian cuando el valor del fondo sube o baja; lo que cambia es el precio de cada unidad. Cada día se divide el valor del fondo entre las unidades que hay, y ese es el NAV del día. Para saber cuánto vale tu cuenta en pesos, multiplicas tus unidades por el NAV.&lt;/p&gt;

&lt;p&gt;Me costó un par de días y varias conversaciones con la IA entenderlo bien. Lo mejor es que cuando el fondo gana o pierde cambia un solo número, y el saldo en pesos de todos se actualiza solo. No tienes que calcular los intereses de cada empleado, ni estar transaccionando todo el tiempo para obtenerlo. Una contribución nueva no mueve el precio: entran más pesos y salen más unidades, en la misma proporción.&lt;/p&gt;

&lt;h2&gt;
  
  
  La arquitectura
&lt;/h2&gt;

&lt;p&gt;Como quería que fuera una aplicación realista, la hice con microservicios en contenedores; cada uno vive en su carpeta dentro de &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/services" rel="noopener noreferrer"&gt;services/&lt;/a&gt;. Existe una API donde se dan de alta los nuevos empleadores y se reciben las contribuciones. Luego hay un proceso que transforma las contribuciones en unidades, y esas unidades se guardan en cuentas individuales dentro de el libro mayor (ledger en inglés).&lt;/p&gt;

&lt;p&gt;El ledger es el motor financiero, es justo el COBOL que estoy reemplazando. Seleccioné &lt;a href="https://tigerbeetle.com" rel="noopener noreferrer"&gt;TigerBeetle&lt;/a&gt;, es open source y el equipo detrás se ha dedicado a afinar esta base de datos a nivel de CPU: revisan constantemente sus algoritmos de ordenamiento y de inserción para ganar microsegundos de desempeño. En este sentido es parecido a COBOL al estar tan acoplado al hardware, aunque con una arquitectura más moderna.&lt;/p&gt;

&lt;p&gt;Escalar TigerBeetle es igual, particionando y creando nuevas instancias. Para este ejercicio cada partición es un fondo independiente; entonces, un fondo se administra completo en una sola instancia, nunca partido en dos para evitar transacciones distribuidas. Imagina que esta empresa ficticia un día decide ir por un contrato grande con un empleador de más de 200,000 o de un millón de empleados, pero este cliente le exige estar aislado y tener su propio fondo. Ese escenario encaja perfecto en el diseño: le das su fondo, lo pones en otra instancia de TigerBeetle y lo escalas por separado del resto de la aplicación.&lt;/p&gt;

&lt;p&gt;En TigerBeetle existen cuentas por cada trabajador, en unidades, con sus abonos y retiros. También existen cuentas en pesos para cada empleador, desde donde se almacenan las contribuciones y se hace la distribución entre todos sus trabajadores. Los pesos y las unidades viven en libros separados dentro del ledger, y una transferencia de un libro al otro es imposible por diseño. El contrato con el ledger está en &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/ledger-client" rel="noopener noreferrer"&gt;libs/ledger-client&lt;/a&gt; y la implementación con TigerBeetle en &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/ledger-tigerbeetle" rel="noopener noreferrer"&gt;libs/ledger-tigerbeetle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Así se ve la infraestructura: por dónde entra una petición, cómo se protege cada conexión y qué piezas crecen agregando réplicas y cuáles creciendo el hardware CPU y RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yv6vdixlac3ycoahj9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yv6vdixlac3ycoahj9s.png" alt="Arquitecture en capas, Gateway, API, Almacenamiento" width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Las APIs crecen solas con la carga. Los procesos de fondo se escalan manualmente, porque hay que cuidar cuántos clientes se conectan al ledger y cómo se reparten las colas. Las bases de datos escalan verticalmente (más CPU y RAM); TigerBeetle, además, crece con un clúster nuevo por fondo.&lt;/p&gt;

&lt;h2&gt;
  
  
  El caché que no puede mentir
&lt;/h2&gt;

&lt;p&gt;Como no quiero estar consultando todo el tiempo al motor financiero y generarle carga innecesaria, hice una proyección, una especie de caché. Aquí hay que ser muy cuidadoso, porque estamos hablando de dinero; a nadie le gusta que la aplicación le muestre menos dinero solamente porque el caché está desactualizado.&lt;/p&gt;

&lt;p&gt;Por eso la proyección no la llenan los servicios. TigerBeetle mismo publica cada movimiento a una cola en RabbitMQ, y un worker lee esos eventos y actualiza la proyección. Un evento puede llegar más de una vez, entonces procesarlo dos afecta no cambia el saldo (idempotencia). Y si un evento no se puede procesar, el proceso no continúa: se manda a una cola de errores y el worker se detiene, porque un hueco en los movimientos es peor que una caída del servicio. El saldo, al final, siempre se lee del ledger.&lt;/p&gt;

&lt;p&gt;Esa proyección vive en Postgres, y ahí también genero los estados de cuenta. Algo que no anticipé es que generar los estados de cuenta es de los procesos más caros. Además,  armar los reportes de fin de día que hay que entregar por regulación es costoso. Los estados de cuenta se guardan en un storage de objetos muy parecido a los buckets de Amazon (mismo API de S3).&lt;/p&gt;

&lt;p&gt;Este es el camino completo de una contribución, y en cada paso, lo que se ve en el dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlww2kcsi4kowbzf9a2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdlww2kcsi4kowbzf9a2v.png" alt="Flujo de una contribución" width="800" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La contribución baja hasta TigerBeetle, y de ahí salen dos caminos: el saldo, que balance-api lee directo del ledger, y el historial, que viaja por RabbitMQ hasta la proyección y de ahí a los estados de cuenta. Los paneles de la derecha son los que reviso en Grafana: si "Mensajes en cola" crece y no baja, la proyección se está quedando atrás; si "Proyección vs ledger" no está en cero, algo no cuadra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source y portable
&lt;/h2&gt;

&lt;p&gt;Traté de que la arquitectura fuera lo más portable a cualquier nube, y cuidé las licencias para evitar sorpresas. Redpanda se quedó fuera porque su licencia (BSL) no es open source, y además ya necesitaba RabbitMQ porque es la cola a la que TigerBeetle publica sus eventos. Redis también se quedó fuera porque cambió su licencia en 2024, y en su lugar usé Valkey, el fork de la Linux Foundation. Para los archivos empecé con MinIO y terminé con SeaweedFS cuando sus imágenes dejaron de poder descargarse libremente; como lo que uso es el API de S3, el impacto fue bajo.&lt;/p&gt;

&lt;p&gt;Todo corre en imágenes de Docker que se suben a Kubernetes: TigerBeetle, PostgreSQL, RabbitMQ, Valkey, SeaweedFS, Keycloak para la identidad, y Prometheus, Loki, Tempo y Grafana para ver qué está pasando. Y el código es público, con licencia MIT: está en &lt;a href="https://github.com/enrique-esquivel/fondaria" rel="noopener noreferrer"&gt;Fondaria&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seguridad: ningún secreto en el repo
&lt;/h2&gt;

&lt;p&gt;Como voy a publicar el código, revisé que no hubiera ni un secreto en el repositorio. Las contraseñas y llaves de desarrollo local se generan cuando levantas el clúster (&lt;a href="https://github.com/enrique-esquivel/fondaria/blob/main/infrastructure/k8s/bootstrap/local-secrets.sh" rel="noopener noreferrer"&gt;local-secrets.sh&lt;/a&gt;), y en producción vienen de un gestor de secretos; si falta uno, el servicio no arranca, nunca usa un valor por defecto.&lt;/p&gt;

&lt;p&gt;Hacia afuera, toda petición entra por un gateway (Envoy) que termina TLS, valida el token contra Keycloak y limita cuántas peticiones puede hacer cada cliente. Adentro, los servicios se hablan entre sí con mTLS usando Linkerd, y nadie puede llamar a un servicio si no está autorizado; eso incluye a Postgres, RabbitMQ, Valkey y el storage de objetos, que solo aceptan a los servicios que de verdad los usan. TigerBeetle es la excepción: no le puse sidecar para no meter un proxy en el camino del ledger, entonces está fuera del mesh y lo protegen las &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/infrastructure/k8s/base/networkpolicies" rel="noopener noreferrer"&gt;NetworkPolicies&lt;/a&gt;, donde todo está bloqueado por default y solo se abre el camino que se necesita. Además, el tráfico entre nodos va cifrado con WireGuard. &lt;/p&gt;

&lt;h2&gt;
  
  
  Los números
&lt;/h2&gt;

&lt;p&gt;Hice las pruebas de desempeño en mi máquina local, que ya es un poco antigua: todo el clúster de Kubernetes corre en un solo nodo con 10 CPUs y 16 GB de RAM. Aun así logré unas 4,000 contribuciones por segundo con tres réplicas del proceso que asigna unidades, y en otra corrida más de 6,000 con cuatro. Cada contribución son dos movimientos en el ledger, o sea que en TigerBeetle eso es del orden de 8,000 a 12,000 transferencias por segundo.&lt;/p&gt;

&lt;p&gt;Lo más interesante es dónde está el cuello de botella. No es TigerBeetle: cada lote de 8,188 transferencias se ejecutó en 30 a 130 milisegundos, y TigerBeetle nunca pasó de 0.2 CPUs. El límite fue Postgres, que guarda la evidencia de cada asignación, y luego el camino que alimenta la proyección. Asignar 100,000 contribuciones y tener la proyección al día tomó 5.6 minutos de punta a punta. O sea, el motor financiero aguanta mucho más, y lo que hay que trabajar es todo lo que está alrededor.&lt;/p&gt;

&lt;p&gt;Después corrí la prueba completa en Google Cloud (GKE) con una topología más parecida a producción: 9 nodos repartidos en tres zonas de disponibilidad (us-east1-b, c y d), con 6 réplicas de TigerBeetle, 3 instancias de Postgres en replicación síncrona y un clúster de 3 nodos de RabbitMQ. Para no dejar corriendo máquinas caras por error, armé los scripts (&lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/infrastructure/gke" rel="noopener noreferrer"&gt;infrastructure/gke&lt;/a&gt;) con un dead man's switch en Cloud Scheduler que destruye el clúster y desvincula la facturación automáticamente si se me olvida apagarlo. Toda la prueba duró 39 minutos prendida.&lt;/p&gt;

&lt;p&gt;En la nube subí la escala a 300,000 empleados repartidos en 12 empresas (600,000 cuentas de inversión y 1.2 millones de eventos en el ledger por cada día hábil). Y ahí salió una lección que nunca hubiera visto en mi máquina local: en las primeras corridas, el cierre diario tardaba entre 10 y 12 minutos aunque los servidores tenían CPU de sobra. El problema no era TigerBeetle ni Postgres, sino la latencia entre zonas combinada con dos configuraciones de las colas: un timeout de lote muy corto (100 ms) que mandaba lotes medios vacíos de 24 filas a Postgres en lugar de 200, y una bandera de Spring AMQP que obligaba a RabbitMQ a hacer una llamada síncrona entre zonas por cada mensaje entregado, topando el flujo en 1,600 mensajes por segundo.&lt;/p&gt;

&lt;p&gt;Ajustando el timeout a 2 segundos y usando prefetch por consumidor, los 3 workers de proyección se repartieron las 16 colas y mantuvieron un promedio de 199.6 filas por lote (de un máximo de 200), proyectando más de &lt;strong&gt;7,300 registros por segundo&lt;/strong&gt;. Con eso, dar de alta a los 300,000 empleados tomó 2 minutos con 5 segundos, recibir las contribuciones de las 12 empresas tomó 1 minuto con 10 segundos, y el cierre completo de fin de día, asignar unidades, proyectar los 1.2 millones de movimientos, conciliar las 600,000 cuentas contra TigerBeetle en 4.8 segundos sin una sola diferencia y guardar el snapshot firmado de 300,000 posiciones bajó a 5 minutos con 56 segundos.&lt;/p&gt;

&lt;p&gt;No creé una página web ni una aplicación web, porque no es el problema que quería resolver, y creo que con la IA eso se puede hacer de manera bastante sencilla. El problema está en el motor financiero y en cómo se distribuyen las transacciones; por eso visualmente lo único que vas a ver es el dashboard en Grafana. También creé algunas &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/demo-tools" rel="noopener noreferrer"&gt;herramientas&lt;/a&gt; para generar datos falsos: la empresa ficticia, empleados ficticios, las contribuciones y el reloj financiero, para ver cómo el fondo gana o pierde todos los días en sus inversiones, cómo eso afecta el valor total del fondo y, a su vez, el valor de las unidades que cada empleado tiene en el sistema. Si quieres correrlo tú, los pasos están en el &lt;a href="https://github.com/enrique-esquivel/fondaria/blob/main/docs/demo/RUNBOOK.md" rel="noopener noreferrer"&gt;runbook del demo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entonces, ¿se puede reemplazar COBOL?
&lt;/h2&gt;

&lt;p&gt;En resumen, la respuesta es sí: sí se puede reemplazar  COBOL en estas condiciones. Obviamente, si eres una empresa grande y puedes pagar un mainframe, todavía parece ser la mejor opción. Pero si eres una empresa pequeña que quiere empezar con pocos clientes e ir creciendo, puedes tener elasticidad y crecer a miles de transacciones por segundo (TPS), la experiencia del usuario va a seguir siendo buena y ahorraras una inversión inicial grande.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/blogs/mt/scaling-for-performance-with-aws-mainframe-modernization-and-micro-focus/" rel="noopener noreferrer"&gt;Scaling for performance with AWS Mainframe Modernization and Micro Focus&lt;/a&gt;, AWS, diciembre 2022: COBOL/CICS en EC2, ~400 transacciones por segundo por c5.large, 3,200 en c5.4xlarge, ~4,000 con 9 instancias.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.techtarget.com/searchdatacenter/definition/CICS" rel="noopener noreferrer"&gt;What is CICS?&lt;/a&gt;, TechTarget, 2021: "According to IBM, CICS processes over 1.1 million transactions per second" (todas las instalaciones del mundo).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/enrique-esquivel/fondaria" rel="noopener noreferrer"&gt;Fondaria&lt;/a&gt;: el código de este blog en GitHub, con licencia MIT. Incluye los servicios, la infraestructura de Kubernetes y las herramientas del demo.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>management</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Is COBOL still "the" engine behind financial applications?</title>
      <dc:creator>Enrique Esquivel Escalante</dc:creator>
      <pubDate>Sun, 01 Nov 2026 00:50:08 +0000</pubDate>
      <link>https://dev.to/enrique_esquivel/is-cobol-still-the-engine-behind-financial-applications-3i91</link>
      <guid>https://dev.to/enrique_esquivel/is-cobol-still-the-engine-behind-financial-applications-3i91</guid>
      <description>&lt;p&gt;This is the first post in a series about lessons I've learned over my career. Today I work as an engineering manager at Google (&lt;em&gt;This is a personal project. The views expressed here are my own and do not represent those of my employer, Google.&lt;/em&gt;), but for more than 15 years I was a consultant, working with companies across many industries. Along the way I ran into problems I couldn't fully solve at the time. Many times I had to deliver solutions that worked, but with limitations, because there wasn't enough time, technology or people.&lt;/p&gt;

&lt;p&gt;With AI I no longer have those limitations. I can explore different solutions and make decisions based on data, and I'd like to finally solve these problems and share what I learned, even if in the end the one reading this is another AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The financial engine nobody "should" touch
&lt;/h2&gt;

&lt;p&gt;I want to start with a problem I ran into many times. When we built new features on top of a financial company's core engine, the users always told me the same thing: we can't change the engine, nothing but COBOL can handle the number of transactions we have.&lt;/p&gt;

&lt;p&gt;And for a long time that was true. IBM builds the mainframe hardware and COBOL runs on top of it; they are tightly coupled, but very efficient. To give you an idea, IBM says that CICS, the transaction system where COBOL runs, processes more than 1.1 million transactions per second when you add up all its customers worldwide (&lt;a href="https://www.techtarget.com/searchdatacenter/definition/CICS" rel="noopener noreferrer"&gt;TechTarget&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The problem is that mainframes are expensive and need constant payments to stay up to date. For a fintech, a small bank or a lender, a mainframe doesn't pay off, so they end up running COBOL on x86 servers, and that's where transactions per second drop dramatically. In a test AWS published with Micro Focus in 2022, a 2-CPU server reached about 400 transactions per second, a 16-CPU server 3,200, and to go past 4,000 they had to run nine servers in parallel (&lt;a href="https://aws.amazon.com/blogs/mt/scaling-for-performance-with-aws-mainframe-modernization-and-micro-focus/" rel="noopener noreferrer"&gt;AWS&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That's when companies running COBOL on x86 hit a wall with their financial engine: they want to grow and they can't. I know what you're thinking: you can scale by adding nodes and partitioning. Yes, COBOL scales that way too; that's exactly what AWS did in that test. But if you're going to partition anyway to reach a few thousand transactions per second, why not replace it? Why keep using it when there are fewer and fewer COBOL experts every year?&lt;/p&gt;

&lt;p&gt;That's where my research started.&lt;/p&gt;

&lt;h2&gt;
  
  
  I didn't want another Hello World
&lt;/h2&gt;

&lt;p&gt;To test a COBOL replacement, I wanted to prove you can process transactions with a more realistic architecture. I didn't want to build a Hello World and just brag about the transactions per second of a very simple example. I wanted something that looked like a real business.&lt;/p&gt;

&lt;p&gt;So I worked with a fictional company that manages pension funds. As you may know, many companies in Latin America use private pension funds; in Mexico these private plans also come with some tax benefits. Once a month employers deposit into the fund, and employees can also contribute their own money. Each employee contributes a different amount, often depending on their salary. All of it is pooled, sent to a low-risk investment fund, and that's how it earns returns. At the end of each day, the fund calculates how much every employee of every participating organization has in their account.&lt;/p&gt;

&lt;p&gt;This seemed like an interesting case to me because the transaction volume is high, especially when employers send contributions at the end of the month. It gets even harder when a new employer signs up, because a fund like this is always looking for new clients, and suddenly you have to add 10,000 or 15,000 new employees.&lt;/p&gt;

&lt;p&gt;This business runs on daily and monthly cycles; to see results I would have had to wait months. So I built a small hack I found interesting: a financial clock that doesn't follow the wall clock. I decide when a business day passes, and with one command I move the fund to the next one: contributions arrive, the day's value is calculated and the day is closed. That way I could see in minutes what would take weeks in real life.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I had to learn, and where AI helped
&lt;/h2&gt;

&lt;p&gt;I have to admit I didn't know much about this kind of business, and AI helped me first to understand it and then to model it. The key piece is something called NAV, the &lt;em&gt;net asset value&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Every contribution becomes a number of units. Those units don't change when the fund's value goes up or down; what changes is the price of each unit. Every day the fund's value is divided by the units in issue, and that's the day's NAV. To know how much your account is worth in pesos, you multiply your units by the NAV.&lt;/p&gt;

&lt;p&gt;It took me a couple of days and several conversations with AI to really get it. The best part is that when the fund gains or loses, a single number changes, and everyone's balance in pesos updates on its own. You don't have to calculate each employee's interest, or keep posting transactions all the time to get it. A new contribution doesn't move the price: more pesos come in and more units go out, in the same proportion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Since I wanted a realistic application, I built it with containerized microservices; each one lives in its own folder under &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/services" rel="noopener noreferrer"&gt;services/&lt;/a&gt;. There's an API where new employers sign up and contributions are received. Then a process converts contributions into units, and those units are stored in individual accounts in the ledger.&lt;/p&gt;

&lt;p&gt;The ledger is the financial engine, exactly the COBOL I'm replacing. I chose &lt;a href="https://tigerbeetle.com" rel="noopener noreferrer"&gt;TigerBeetle&lt;/a&gt;: it's open source, and the team behind it has dedicated itself to tuning this database at the CPU level. They constantly revisit their sorting and insertion algorithms to win microseconds of performance. In that sense it's similar to COBOL in how close it sits to the hardware, but with a more modern architecture.&lt;/p&gt;

&lt;p&gt;Scaling TigerBeetle works the same way, by partitioning and adding instances. In this exercise each partition is an independent fund, so a fund is managed entirely in one instance, never split in two, to avoid distributed transactions. Imagine this fictional company decides one day to go after a big contract with an employer that has more than 200,000 or even a million employees, but this client demands to be isolated and have its own fund. That scenario fits the design perfectly: you give it its own fund, put it on another TigerBeetle instance and scale it separately from the rest of the application.&lt;/p&gt;

&lt;p&gt;In TigerBeetle there are accounts for each worker, in units, with their credits and debits. There are also accounts in pesos for each employer, where contributions are held and then distributed among all its workers. Pesos and units live in separate ledgers inside TigerBeetle, and a transfer from one to the other is impossible by design. The ledger contract is in &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/ledger-client" rel="noopener noreferrer"&gt;libs/ledger-client&lt;/a&gt; and the TigerBeetle implementation in &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/ledger-tigerbeetle" rel="noopener noreferrer"&gt;libs/ledger-tigerbeetle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is what the infrastructure looks like: where a request comes in, how each connection is protected, and which pieces scale by adding replicas and which by adding CPU and RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fndawj22rhdemu787u9p6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fndawj22rhdemu787u9p6.png" alt="Architecture Diagram in layers, Gateway, API, Storage " width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The APIs scale on their own with load. Background processes are scaled manually, because you have to watch how many clients connect to the ledger and how the queues are split. The databases scale vertically (more CPU and RAM); TigerBeetle also grows with a new cluster per fund.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cache that can't lie
&lt;/h2&gt;

&lt;p&gt;Since I don't want to query the financial engine all the time and put unnecessary load on it, I built a projection, a kind of cache. You have to be very careful here, because we're talking about money: nobody likes an app that shows them less money just because the cache is stale.&lt;/p&gt;

&lt;p&gt;That's why the services don't fill the projection. TigerBeetle itself publishes every movement to a RabbitMQ queue, and a worker reads those events and updates the projection. An event can arrive more than once, so processing it twice doesn't change the balance (idempotency). And if an event can't be processed, the process doesn't keep going: the event goes to an error queue and the worker stops, because a gap in the movements is worse than downtime. In the end, the balance is always read from the ledger.&lt;/p&gt;

&lt;p&gt;That projection lives in Postgres, and that's also where I generate account statements. Something I didn't anticipate is that generating statements is one of the most expensive processes. Building the end-of-day reports required by regulators is costly too. Statements are stored in an object store very similar to Amazon buckets (same S3 API).&lt;/p&gt;

&lt;p&gt;This is the full path of a contribution, and what you see on the dashboard at each step:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1rauv4rpuc4t38wqqxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy1rauv4rpuc4t38wqqxe.png" alt="Data flow from API, DB, Worker, ledger, CDC, DB, to Statement in S3" width="800" height="888"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The contribution goes down to TigerBeetle, and from there it takes two paths: the balance, which balance-api reads directly from the ledger, and the history, which travels through RabbitMQ to the projection and from there to the statements. The panels on the right are the ones I watch in Grafana: if "Queued messages" grows and doesn't come down, the projection is falling behind; if "Projection vs ledger" isn't zero, something doesn't add up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source and portable
&lt;/h2&gt;

&lt;p&gt;I tried to make the architecture as portable as possible to any cloud, and I was careful with licenses to avoid surprises. Redpanda was left out because its license (BSL) isn't open source, and I already needed RabbitMQ because it's the queue TigerBeetle publishes its events to. Redis was left out too because it changed its license in 2024, so I used Valkey, the Linux Foundation fork, instead. For files I started with MinIO and ended up with SeaweedFS when MinIO's images stopped being freely downloadable; since what I use is the S3 API, the impact was small.&lt;/p&gt;

&lt;p&gt;Everything runs as Docker images deployed to Kubernetes: TigerBeetle, PostgreSQL, RabbitMQ, Valkey, SeaweedFS, Keycloak for identity, and Prometheus, Loki, Tempo and Grafana to see what's going on. And the code is public, under the MIT license: it's on GitHub as &lt;a href="https://github.com/enrique-esquivel/fondaria" rel="noopener noreferrer"&gt;Fondaria&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security: no secrets in the repo
&lt;/h2&gt;

&lt;p&gt;Since I was going to publish the code, I made sure there wasn't a single secret in the repository. Local development passwords and keys are generated when you bring up the cluster (&lt;a href="https://github.com/enrique-esquivel/fondaria/blob/main/infrastructure/k8s/bootstrap/local-secrets.sh" rel="noopener noreferrer"&gt;local-secrets.sh&lt;/a&gt;), and in production they come from a secrets manager; if one is missing, the service doesn't start. It never falls back to a default value.&lt;/p&gt;

&lt;p&gt;On the outside, every request comes in through a gateway (Envoy) that terminates TLS, validates the token against Keycloak and limits how many requests each client can make. On the inside, services talk to each other over mTLS with Linkerd, and nobody can call a service unless they're authorized; that includes Postgres, RabbitMQ, Valkey and the object store, which only accept the services that actually use them. TigerBeetle is the exception: I didn't give it a sidecar so there's no proxy in the ledger's path, so it sits outside the mesh and is protected by &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/infrastructure/k8s/base/networkpolicies" rel="noopener noreferrer"&gt;NetworkPolicies&lt;/a&gt;, where everything is blocked by default and only the paths that are needed are opened. On top of that, traffic between nodes is encrypted with WireGuard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;p&gt;I ran the performance tests on my local machine, which is a bit old by now: the whole Kubernetes cluster runs on a single node with 10 CPUs and 16 GB of RAM. Even so, I reached about 4,000 contributions per second with three replicas of the process that allots units, and more than 6,000 with four in another run. Each contribution is two movements in the ledger, so in TigerBeetle that's on the order of 8,000 to 12,000 transfers per second.&lt;/p&gt;

&lt;p&gt;The most interesting part is where the bottleneck is. It isn't TigerBeetle: each batch of 8,188 transfers ran in 30 to 130 milliseconds, and TigerBeetle never went above 0.2 CPUs. The limit was Postgres, which stores the evidence for every allotment, and then the path that feeds the projection. Allotting 100,000 contributions and having the projection caught up took 5.6 minutes end to end. In other words, the financial engine can take much more; what needs work is everything around it.&lt;/p&gt;

&lt;p&gt;Then I ran the full test on Google Cloud (GKE) with a more production-like topology: 9 nodes spread across three availability zones (us-east1-b, c and d), with 6 TigerBeetle replicas, 3 Postgres instances with synchronous replication and a 3-node RabbitMQ cluster. So I wouldn't leave expensive machines running by mistake, I wrote the scripts (&lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/infrastructure/gke" rel="noopener noreferrer"&gt;infrastructure/gke&lt;/a&gt;) with a dead man's switch in Cloud Scheduler that destroys the cluster and unlinks billing automatically if I forget to shut it down. The whole test ran for 39 minutes.&lt;/p&gt;

&lt;p&gt;In the cloud I raised the scale to 300,000 employees across 12 companies (600,000 investment accounts and 1.2 million ledger events per business day). And there I learned something I would never have seen on my local machine: in the first runs, the daily close took 10 to 12 minutes even though the servers had CPU to spare. The problem wasn't TigerBeetle or Postgres. It was cross-zone latency combined with two queue settings: a very short batch timeout (100 ms) that sent half-empty batches of 24 rows to Postgres instead of 200, and a Spring AMQP flag that forced RabbitMQ to make a synchronous cross-zone call for every delivered message, capping throughput at 1,600 messages per second.&lt;/p&gt;

&lt;p&gt;After raising the timeout to 2 seconds and using per-consumer prefetch, the 3 projection workers split the 16 queues between them and kept an average of 199.6 rows per batch (out of a maximum of 200), projecting more than &lt;strong&gt;7,300 records per second&lt;/strong&gt;. With that, signing up the 300,000 employees took 2 minutes 5 seconds, receiving contributions from the 12 companies took 1 minute 10 seconds, and the full end-of-day close dropped to 5 minutes 56 seconds. That close covers allotting units, projecting the 1.2 million movements, reconciling the 600,000 accounts against TigerBeetle in 4.8 seconds without a single difference, and saving the signed snapshot of 300,000 positions.&lt;/p&gt;

&lt;p&gt;I didn't build a web page or a web app, because that's not the problem I wanted to solve, and I think with AI that part is pretty easy to do. The problem is the financial engine and how transactions are distributed; that's why the only visual you'll see is the Grafana dashboard. I also built some &lt;a href="https://github.com/enrique-esquivel/fondaria/tree/main/libs/demo-tools" rel="noopener noreferrer"&gt;tools&lt;/a&gt; to generate fake data: the fictional company, fictional employees, contributions and the financial clock, to see how the fund gains or loses on its investments every day, how that affects the fund's total value and, in turn, the value of the units each employee holds. If you want to run it yourself, the steps are in the &lt;a href="https://github.com/enrique-esquivel/fondaria/blob/main/docs/demo/RUNBOOK.md" rel="noopener noreferrer"&gt;demo runbook&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, can COBOL be replaced?
&lt;/h2&gt;

&lt;p&gt;In short, the answer is yes: COBOL can be replaced under these conditions. Obviously, if you're a large company and can afford a mainframe, it still seems to be the best option. But if you're a small company that wants to start with a few clients and grow, you can have elasticity and scale to thousands of transactions per second (TPS), the user experience will stay good, and you'll avoid a large upfront investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/blogs/mt/scaling-for-performance-with-aws-mainframe-modernization-and-micro-focus/" rel="noopener noreferrer"&gt;Scaling for performance with AWS Mainframe Modernization and Micro Focus&lt;/a&gt;, AWS, December 2022: COBOL/CICS on EC2, ~400 transactions per second per c5.large, 3,200 on c5.4xlarge, ~4,000 with 9 instances.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.techtarget.com/searchdatacenter/definition/CICS" rel="noopener noreferrer"&gt;What is CICS?&lt;/a&gt;, TechTarget, 2021: "According to IBM, CICS processes over 1.1 million transactions per second" (all installations worldwide).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/enrique-esquivel/fondaria" rel="noopener noreferrer"&gt;Fondaria&lt;/a&gt;: the code behind this post on GitHub, MIT licensed. It includes the services, the Kubernetes infrastructure and the demo tools.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>kubernetes</category>
      <category>fintech</category>
    </item>
  </channel>
</rss>
