Cómo reconstruimos una plataforma legacy sin (casi) morir en el intento


Una tarde lluviosa de octubre, después de trabajar, recibí la llamada de un amigo. Me dijo: “Me ha entrado un trabajo y te necesito”, así que quedamos y me explicó en qué consistía el tema.

Era una plataforma algo antigua, pero aun así sólida, que necesitaba un refactor de pies a cabeza. No puedo decir nombres, he firmado cosas, pero es un líder a nivel nacional en eventos, con decenas de miles de asistentes a sus eventos.

Y quedaba poco para el siguiente evento, menos de seis meses. El desafío estaba ahí, así que empecé a empeñar todas las tardes y noches durante esos seis meses. Una carrera que la miro ahora y digo: “¿Pero dónde te metiste?”, pero la verdad es que no me arrepiento de nada. Aprendimos mucho, crecimos mucho y, al final, todo salió bien.

Todo empezó analizando lo que tenían: un monolito enorme, íntegramente hecho en PHP + Blade (nada malo, no me malentendáis), que necesitaba una máquina con más cores y RAM que GPT un día de resaca. Después de varios días dándole vueltas, decidimos partirlo a cachitos. Un front/back en Next.js; seguiríamos con el código antiguo haciéndole una API (de esta decisión nos arrepentimos pronto, pero lo solucionamos).

Decidimos mover ese pedazo de servidor al cloud. Y empezamos a montar máquinas; fue una buena migración, no tuvimos nada de downtime. Escogimos una instancia mediana de Google Cloud, pusimos ahí el monolito y separamos la base de datos. De momento, hasta que no llegase el evento, no necesitaríamos mucha máquina, así que eso ahorraría costes al cliente y podríamos trabajar desde allí. Además, nos llevamos otras webs que tenían y las centralizamos en un lugar.

Solo con eso pudimos cerrar un servidor que era carísimo, dejar de depender del “metal” (que tampoco es malo) y empezar en el cloud.

Para el frontend en Next.js decidimos usar Cloud Run y Cloud Build. Creamos una imagen de Docker para el Next.js y las pipelines de CI/CD. Ahora todo pull request a la rama main acabaría en el Cloud Run de test y preparamos un Load Balancer para que la dirección llevase a la máquina de front y un clásico test.example.com para probar lo nuevo. Creamos una máquina de staging para probar el backend “nuevo” y yo empecé a trabajar en el frontend y la integración del backend con Typesense (en otro servidor, todo dentro de la misma red de Google Cloud) para las búsquedas, mientras mi compañero se dedicaba a crear la API del back. Hasta aquí todo perfecto, íbamos on-time y sin problemas. La idea era acabar antes del evento para que el cliente pudiese probarlo todo bien y salir sin problemas. Pero en este trabajo, todos sabemos que siempre salen problemas.

Cuando el front empezaba a tener cara y ojos y teníamos todos los endpoints y workflows de la plataforma preparados en su API (recordemos que estaba en el proyecto antiguo), empezamos a probar con datos reales… y encontramos un problema. No fue buena idea. El código antiguo arrastraba aún un montón de problemas; cada request a la API tardaba demasiado, segundos, pero nosotros no queríamos eso, queríamos la excelencia. Así que empezamos a hacerlo todo de cero. No un refactor: empezamos todo el backend del negocio de cero. Optimizando cada llamada, cada segundo, cada conexión, rehicimos la integración con el motor de búsqueda y casi no llegamos.

Así que aquí teníamos ya montada la nueva estructura de forma casi definitiva: Cloud Run / Cloud Build para Next.js en dos entornos (test y producción), Load Balancer para llevar el tráfico y escalar el frontend lo que fuera necesario, y Cloud Run perfectamente configurado para que no nos diese un susto con la facturación. Una máquina para Typesense y otra para la API nueva. Después, otra máquina con las webs antiguas y el proyecto antiguo como backup, por si teníamos que volver a lo anterior por alguna emergencia.

Corriendo, y casi en el último momento (a una semana o dos antes del evento), estaba todo listo y probado para ser puesto en producción. Así que después de un finde intensísimo de horas y horas de llamadas y estrés, llegamos. Cambiamos el Load Balancer para que apuntase al Cloud Run de producción. Cambiamos las pipelines y pusimos todas las normas de Cloudflare como tocaba para el nuevo frontend.

¿Pensabas que ya estaba, eh? Pues no. Al abrir al mundo real nos dimos de bruces con que la base de datos nos estaba haciendo de cuello de botella. Por temas de negocio, esta sí que la tuvimos que mantener entera, pero no estaba lo suficientemente optimizada para la instancia donde estaba puesta. Antes de escalarla, optimizamos todas las consultas que se nos atragantaban, añadimos nuevos índices, vistas… y nos quedamos un poco calvos de tanto optimizar. Antes, en la máquina anterior, a base de potencia esto no se notaba, pero ahora estábamos en una instancia más justa (queríamos ahorrar costes al cliente). Al final ganamos muchísima más velocidad, pero decidimos escalar la base de datos a algo más potente, solo por tener la seguridad de que en medio del evento no se iba a caer nada.

Ahora sí, llegamos al día del evento y, quitando algún detalle menor, todo fue perfectamente bien. El nuevo sistema respondió. Poco a poco se ha ido puliendo más y mejorando significativamente. Donde antes una búsqueda tardaba segundos

Mirando atrás, el esfuerzo valió la pena. No solo el sistema aguantó su primer evento sin una sola caída, sino que conseguimos una mejora de rendimiento de casi el 80%. Pasar de un monolito pesado a una arquitectura que responde de forma instantánea es, sinceramente, la mayor recompensa después de tantas noches sin dormir.

Unos meses después del primer evento (viví dos), yo ya me desvinculé, pues bastante tenía ya con un trabajo y mi salud mental flaqueaba. Pero ha sido algo muy divertido de hacer. Al final me he ahorrado muchos detalles técnicos que quedan muy bien en un CV, pero tampoco quiero que ese sea el tono de este blog.