
Juego multijugador en tiempo real que sobrevive a la caída de su propia base de datos. Cuatro servicios independientes, colas de mensajes y réplicas para mantener la partida viva.
Este proyecto nació de una pregunta incómoda en la clase de Sistemas Distribuidos: ¿qué pasa si la base de datos se cae justo cuando alguien está jugando? La respuesta obvia era "el juego se rompe". Pero la respuesta correcta, la que valía la pena construir, era "el juego sigue".
Un blackjack multijugador tiene tres características que lo hacen interesante desde el punto de vista de sistemas:
La solución naive es meter todo en un solo servidor con una base de datos y rezar. Pero eso no es un sistema distribuido: es un monolito con un solo punto de fallo.
La arquitectura que terminé construyendo separa dos cosas que normalmente viven juntas:
Esta separación es la que permite la tolerancia a fallos. Si PostgreSQL se cae, el Game Engine sigue sirviendo partidas desde memoria. Cuando la base vuelve, un Worker Consumer procesa los mensajes que se encolaron en RabbitMQ durante la caída y sincroniza todo.
La parte que más me gustó implementar fue el modo degradado. Cuando el sistema detecta que PostgreSQL no responde:
Cuando la base de datos se recupera, el Worker Consumer empieza a consumir la cola y aplica los cambios pendientes. La consistencia es eventual, no inmediata, y eso está bien: el usuario prefiere ver su partida guardada 20 segundos después que perderla.
El Main App corre en tres réplicas detrás de NGINX. No es por escalabilidad real (este proyecto no tiene millones de usuarios), es por demostrar que sé hacerlo: health checks, sesiones sin estado con JWT, y un proxy que sabe cuándo sacar una instancia del pool.
La observabilidad la resolví con Prometheus + Grafana. Cada servicio expone /metrics con métricas estándar de Node.js más algunas de negocio: jugadores activos, partidas en curso, mensajes encolados. Los dashboards de Grafana los dejé pre-configurados en el repositorio, así que cualquiera puede levantar el sistema con ./deploy.sh y ver las métricas funcionando en menos de un minuto.
Este proyecto me enseñó que la tolerancia a fallos no es magia, es diseño. No se trata de que nada falle, sino de que cuando algo falle, el sistema se comporte de una forma predecible y útil.
También aprendí que WebSockets y bases de datos no se llevan bien si los mezclas sin pensar. Mi primera versión intentaba guardar cada jugada en PostgreSQL en tiempo real. Era lento y frágil. La solución fue tratar la base como un registro eventual, no como la fuente de verdad del juego.
Y sobre todo, aprendí que la parte más difícil de un sistema distribuido no es hacerlo funcionar, sino hacerlo entendible. Un sistema que solo tú sabes levantar no sirve. Por eso el README tiene un script de despliegue, dashboards listos, y variables de entorno documentadas.
Este proyecto fue el trabajo final de Sistemas Distribuidos en la UPTC, desarrollado junto a David Rodríguez y Santiago Naranjo. Yo me encargué de la arquitectura, el Game Engine, la parte de tolerancia a fallos y la integración frontend. David trabajó en el sistema de autenticación y Santiago en el login con JWT y las pruebas. Fue un buen ejercicio de dividir un problema grande en partes que se pudieran trabajar en paralelo sin pisarse.