TPTito Pinardo Gutiérrez
← Volver al rack
English
1U · BATTLEPROSRust · Folia · PostgreSQL Abierto a pruebas externas

Posición 12 de 26 en el rack · En producción

Unidad 1U · Proyecto personal en marcha

BattlePros

Abierto a pruebas · 0.7.45

Una red de Minecraft hecha a medida: un solo mundo repartido entre varios servidores, sin pantallas de carga al pasar de uno a otro, y todo lo que hay detrás (entrada, mando, agentes, panel) escrito desde cero.

Mi parte
La idea, la arquitectura, las decisiones y las pruebas. El código lo escribo con un agente de IA como compañero, con reglas fijas: medir antes y después, documentar el porqué y no tocar la jugabilidad.
Dónde vive
En este mismo servidor: el mando y los fragmentos en un contenedor y la entrada pública aislada en la DMZ, en otro. Sin nada en mi PC.
Pruebas y producción
Todo se probó con dos zonas (dos «continentes» a 80 ms de distancia) para comprobar que el reparto, los cruces y la alta disponibilidad funcionan. En producción, para las pruebas externas, hay una sola: el goliat de este servidor.
  • 20 TPScon 100 bots a la vez en la zona
  • −80 %en pagar a otro jugador (/pay), tras juntar 5 viajes a la base en uno
  • 2 → 1zonas: dos en pruebas, una en producción
  • 0agujeros en la batería de suplantación de cuentas
  • 1 ordenpara sumar un servidor físico nuevo a la red
  • 2,35×más rápido generando terreno con el generador en Rust

La red por dentro

Pulsa una pieza o elige un recorrido: entrar, cruzar de un servidor a otro sin pantalla de carga, añadir un servidor con una orden…

Fuera Un goliat · contenedores en Proxmox una zona en producción DMZ · lo único visible desde internet Control · detrás del inicio de sesión Fragmentos · el mundo repartido Mundo, agentes y copias Jugador Mojang DNS (SRV) Router Otro jugador Yo (admin) Forgejo CI Otro servidor Entrada (Rust) Voz Cortafuegos Authelia Panel Mando (Rust) PostgreSQL eu-1 eu-2 Coliseo Generador Almacén Agente Copias Métricas
Hermesde visita en la unidad

Pulsa una pieza o elige un recorrido. Si vienes de una habilidad, ya están encendidas las piezas donde la uso.

      Entras al servidor
      1. Jugador — Escribes el dominio en Minecraft.
      2. DNS con registro SRV — El DNS responde con un registro SRV: a qué puerto ir.
      3. Router — El router solo deja pasar ese puerto, y solo hacia la DMZ.
      4. Cortafuegos de la DMZ — El cortafuegos de la DMZ deja hablar a la entrada solo con lo justo.
      5. La entrada (Rust) — La entrada cifra la conexión y pide a tu cliente que se identifique.
      6. Sesiones de Mojang — Mojang confirma que la cuenta es tuya y trae tu skin.
      7. La entrada (Rust) — Si la red está llena (las plazas del panel), aquí te lo dice.
      8. El mando (Rust) — El mando sabe qué fragmento lleva la zona donde estabas.
      9. La entrada (Rust) — La entrada te conecta con él.
      10. Fragmento eu-1 (Folia) — eu-1 te recibe con tus datos de la base.
      11. Jugador — Ya estás jugando.
      Cruzas a otro servidor sin pantalla de carga
      1. Jugador — Caminas hacia una zona que lleva otro fragmento.
      2. Fragmento eu-1 (Folia) — eu-1 ve que te acercas al borde de su celda.
      3. El mando (Rust) — El mando confirma quién lleva la celda de al lado.
      4. La entrada (Rust) — La entrada abre por debajo una conexión con eu-2…
      5. Fragmento eu-2 (Folia) — …que ya te tiene preparado al otro lado.
      6. Almacén del mundo (Rust) — eu-2 carga el terreno del almacén común.
      7. Fragmento eu-2 (Folia) — La entrada cambia el flujo de paquetes al vuelo: un empalme.
      8. Jugador — Sigues andando. No has visto ninguna pantalla de carga.
      Hablas con alguien de otro servidor
      1. Jugador — Escribes en el chat.
      2. La entrada (Rust) — La entrada lo pasa a tu fragmento.
      3. Fragmento eu-1 (Folia) — eu-1 lo filtra (palabrotas, spam, silenciados) y se lo enseña a los de aquí.
      4. PostgreSQL — PostgreSQL lo reparte con LISTEN/NOTIFY a los demás fragmentos.
      5. Fragmento eu-2 (Folia) — eu-2 lo pinta igual que si fuera suyo.
      6. La entrada (Rust) — Llega como mensaje del sistema: sin el aviso de «no se puede verificar».
      7. Otro jugador — Tu amigo lo lee, aunque esté en otro servidor.
      Añado un servidor físico con una orden
      1. Administrador — Entro en el panel desde casa.
      2. Authelia (inicio de sesión) — Authelia comprueba que soy yo.
      3. Panel de administración — En «Servidores» le doy un nombre al servidor nuevo.
      4. El mando (Rust) — El mando crea una ficha de un solo uso que caduca.
      5. Panel de administración — El panel me enseña la orden, una sola vez.
      6. Administrador — La copio…
      7. Un servidor físico nuevo — …y la pego como root en el servidor nuevo.
      8. El mando (Rust) — Antes de bajar nada, comprueba la huella de la CA del mando: nadie en medio puede cambiar el guion.
      9. Un servidor físico nuevo — Instala Java, el usuario, el agente (comprobando su suma) y el servicio.
      10. Agente (uno por máquina) — El agente se da de alta con la ficha y recibe su certificado.
      11. El mando (Rust) — Ya aparece en el panel: le pongo servicios y fragmentos.
      Cambio las plazas de toda la red
      1. Administrador — Quiero que entren 300 en vez de 500.
      2. Authelia (inicio de sesión) — Paso por Authelia.
      3. Panel de administración — En «Red» cambio las plazas y guardo.
      4. El mando (Rust) — El mando lo apunta en el registro (quién y cuándo)…
      5. PostgreSQL — …y lo guarda en la base.
      6. El mando (Rust) — En el siguiente latido se lo manda a cada agente.
      7. Agente (uno por máquina) — El agente se lo pasa a la entrada de su máquina.
      8. La entrada (Rust) — La lista de servidores enseña el nuevo máximo y, con la red llena, la entrada no deja pasar a nadie nuevo.
      9. Otro jugador — Los que ya están dentro siguen jugando: a nadie se le echa.
      Sale una versión nueva
      1. Forgejo (CI y releases) — Una etiqueta en Forgejo compila, prueba y publica la release con su suma.
      2. El mando (Rust) — Fijo la versión en el panel: el mando la baja y comprueba la suma.
      3. Agente (uno por máquina) — Los agentes ven que su versión ya no es la deseada.
      4. Fragmento eu-1 (Folia) — eu-1 avisa en el juego, se para, se actualiza y vuelve…
      5. Agente (uno por máquina) — …y solo cuando está bien, sigue el siguiente.
      6. Fragmento eu-2 (Folia) — eu-2, igual: de uno en uno, nunca todos a la vez.
      7. Agente (uno por máquina) — Mientras, sus jugadores han pasado al fragmento que sigue en marcha.
      8. Coliseo (liga) — El coliseo, el último.
      9. El mando (Rust) — El mando lo da por hecho cuando todos están en marcha con la versión nueva.
      Se cae un fragmento
      1. Fragmento eu-2 (Folia) — eu-2 se cuelga o se queda sin memoria.
      2. Agente (uno por máquina) — Su agente deja de verlo sano y lo cuenta.
      3. El mando (Rust) — El mando ve que hay celdas sin quien las lleve.
      4. PostgreSQL — Las reasigna en la base, de una vez y sin duplicados.
      5. El mando (Rust) — Avisa a los fragmentos que quedan.
      6. Fragmento eu-1 (Folia) — eu-1 se queda también con las celdas de eu-2.
      7. La entrada (Rust) — La entrada lleva a los jugadores al fragmento que sigue vivo.
      8. Jugador — Pierdes unos segundos, no la partida. El agente vuelve a arrancar eu-2.
      1. Jugador

        Cualquiera con una cuenta original de Minecraft. Solo cuentas de Mojang: nadie puede entrar haciéndose pasar por otro.

      2. Sesiones de Mojang

        La entrada pregunta a Mojang si la cuenta es de verdad (y trae su skin). Si no responde a tiempo, no se entra.

      3. DNS con registro SRV

        El jugador escribe solo el dominio; un registro SRV le dice a qué puerto ir. Sin proxy de Cloudflare: Minecraft no es HTTP.

        • Technitium DNS
      4. Router

        Reenvía el puerto del juego y la voz solo hacia la DMZ. Desde la DMZ solo se puede hablar con los puertos exactos del juego.

        • MikroTik
        • VLANs
      5. Otro jugador

        Alguien en otra parte del mundo, quizá en otro servidor. Para él es el mismo mundo y el mismo chat.

      6. Administrador

        Yo desde el navegador: el panel está detrás de Authelia y solo se ve desde casa.

      7. Forgejo (CI y releases)

        Cada pieza tiene su repositorio: en cada push se compila y se prueba, y con una etiqueta sale una release con su suma SHA-256.

        • Forgejo · Actions
        • Git
      8. Un servidor físico nuevo

        Cualquier Linux con systemd: se le pega una orden y se instala solo (Java, usuario, agente y alta), comprobando antes la huella de la CA.

        • Linux (Debian, Alpine, Arch)
      9. La entrada (Rust)

        Mi proxy de Minecraft: cifrado y login con Mojang, cola de plazas y el empalme que pasa al jugador de un servidor a otro sin pantalla de carga.

        • Rust
        • Linux / SSH hardening
      10. Chat de voz

        El relé UDP del chat de voz por proximidad, en la misma entrada: la voz no toca los fragmentos.

        • Rust
      11. Cortafuegos de la DMZ

        La entrada vive aislada en su VLAN. Si alguien la rompiera, solo podría hablar con los puertos del juego, nunca con la base de datos ni con el resto de casa.

        • VLANs
        • Linux / SSH hardening
      12. Authelia (inicio de sesión)

        El panel no tiene usuarios propios: pasa por el inicio de sesión único de casa y el mando apunta quién hizo cada cosa.

        • Linux / SSH hardening
      13. Panel de administración

        Una web en Svelte: servidores, fragmentos, jugadores, staff, plazas, anuncios a toda la red, versiones, mapa en vivo y monitorización.

        • JavaScript / TypeScript
      14. El mando (Rust)

        El cerebro: decide qué servidor lleva cada trozo del mundo, guarda el estado deseado, da de alta máquinas con fichas de un solo uso y reparte versiones.

        • Rust
        • PostgreSQL
      15. PostgreSQL

        Economía, casas, equipos, sanciones y el reparto del mundo. Lo que da o quita algo se comprueba siempre aquí, en una sola sentencia.

        • PostgreSQL
      16. Fragmento eu-1 (Folia)

        Un servidor de juego con mi versión de Folia: lleva unas celdas del mundo. Con gente cerca, la zona entera se queda en un mismo fragmento para no partir bases ni redstone.

        • Java (Paper/Folia)
      17. Fragmento eu-2 (Folia)

        Otro trozo del mismo mundo. Si eu-1 cae, sus celdas pasan aquí y los jugadores siguen jugando.

        • Java (Paper/Folia)
      18. Coliseo (liga)

        Un fragmento solo para la liga: duelos con kits, elo, retos y temporadas, sin cargar el mundo principal.

        • Java (Paper/Folia)
      19. Generador de terreno (Rust)

        Genera el terreno nuevo fuera de Folia, con la misma semilla y un 99,3 % de bloques iguales que el original. Folia solo pone encima las estructuras.

        • Rust
      20. Almacén del mundo (Rust)

        Guarda los chunks comprimidos con zstd y un diccionario (7,5 veces más pequeños). Cualquier fragmento puede cargar cualquier zona.

        • Rust
      21. Agente (uno por máquina)

        Arranca, para y actualiza los fragmentos de su máquina según lo que diga el mando, y le manda métricas cada pocos segundos.

        • Rust
        • Linux (Debian, Alpine, Arch)
      22. Copias (PBS)

        Copia diaria y cifrada del contenedor y de la base de datos, con la prueba de restauración semanal de todo el homelab.

        • Proxmox Backup Server
      23. Métricas

        TPS, MSPT, memoria, CPU, red y disco de cada fragmento y cada máquina, guardados en el propio mando: 14 días de gráficas en el panel.

        • PostgreSQL

      Lo que uso de mi árbol

      Pulsa una habilidad: se encienden en el mapa las piezas donde la uso y te cuento para qué.

      Rust
      Entrada, mando, agente, almacén y generador: todo lo que no es el juego en sí.
      Java (Paper/Folia)
      El plugin del juego sobre Folia 26.3: economía, casas, equipos, liga, staff y chat de toda la red.
      PostgreSQL
      Todo el estado del juego y del reparto; sentencias únicas para que nada se duplique y LISTEN/NOTIFY para avisar entre servidores.
      JavaScript / TypeScript
      El panel de administración en Svelte 5 y TypeScript.
      Linux (Debian, Alpine, Arch)
      Contenedores Debian con systemd de usuario; el agente se instala en cualquier Linux con una orden.
      Proxmox VE
      Los dos contenedores viven en el Proxmox de casa, junto al resto del homelab.
      LXC
      El mando y los fragmentos en un contenedor; la entrada, en otro aislado en la DMZ.
      Terraform
      Los contenedores, su red y su DNS, declarados en Terraform.
      Ansible
      Roles para el contenedor de desarrollo y para la entrada de la DMZ, desplegados desde Forgejo.
      Forgejo · Actions
      Un repositorio por pieza con CI y releases firmadas por su suma.
      Git
      Ramas por cambio, etiquetas por versión y la wiki de resultados versionada.
      VLANs
      La entrada pública en la VLAN de la DMZ, sin acceso al resto de casa.
      MikroTik
      Reenvío del puerto del juego y la voz, y reglas de la DMZ hacia los puertos justos.
      Technitium DNS
      Registro SRV para entrar solo con el dominio, creado por API.
      Linux / SSH hardening
      Solo cuentas de Mojang, mTLS entre servicios, base de datos sin exponer y batería de suplantación sin agujeros.
      Proxmox Backup Server
      Copias diarias y cifradas con prueba de restauración semanal.

      Antes y después, medido

      Cada cambio se mide contra la versión anterior con la misma prueba (bots haciendo de todo durante 6 minutos) y se queda solo si mejora sin romper nada. Algunas medidas son de la zona lejana de pruebas, a 80 ms de la base: ahí es donde más se notan los viajes.

      antes después

      Base de datos

      • /pay (pagar a otro) −80 %

        707 ms 140 ms

        Cinco viajes a la base (buscar el nombre, transacción, avisos) juntados en una sola sentencia.

      • /sethome (guardar una casa) −69 %

        461 ms 144 ms

        Bloquear, contar, poner, leer y COMMIT: cinco viajes, ahora uno.

      • /perfil −65 %

        393 ms 138 ms

        Perfil y saldos en una consulta con subconsultas en vez de cuatro.

      • Vender con 10 a la vez −69 %

        2768 ms 852 ms

        La venta entera y su COMMIT en un solo envío: el cerrojo del precio dura un viaje.

      • /msg en el mismo servidor −96 %

        84 ms 3 ms

        Si el otro juega en el mismo fragmento, el mensaje no pasa por la base.

      Servidor de juego

      • TPS con 100 bots +9 %

        17,4 TPS 19 TPS

        Folia usaba un solo hilo de región con 12 núcleos: ahora se reparten.

      • MSPT con 100 bots −21 %

        33,7 ms 26,7 ms

        Lo mismo: menos milisegundos por tick y sin la parada de la región global.

      • TPS de noche con 40 jugadores +53 %

        10,3 TPS 15,8 TPS

        Rangos de activación de la IA, aparición cada N ticks y colisiones más baratas.

      • Abrir un menú que lee de la base −93 %

        270 ms 19 ms

        Lo que solo se enseña, en memoria; lo que da o quita, en la base.

      Mundo, red y disco

      • Generar terreno nuevo ×2,3

        23 chunks/s 54 chunks/s

        El terreno lo genera un servicio en Rust; Folia solo pone las estructuras.

      • Compresión del mundo +9 %

        6,9 × 7,5 ×

        zstd con un diccionario entrenado con chunks reales (el End, de 17× a 25×).

      • CPU de la entrada con 100 bots −34 %

        28,6 % 18,8 %

        Caché de chunks ya comprimidos: el 93 % de aciertos con 42 MB.

      • /pay p95 al cambiar de disco −55 %

        465 ms 208 ms

        La base de pruebas pasó del RAID 1 de sistema al RAID 10 de discos SAS.

      Datos de la wiki de resultados del proyecto (p50 salvo que se diga otra cosa). Las pruebas se hicieron con dos zonas para comprobar que todo funciona entre continentes; en producción hay una sola.

      Bitácora de incidencias

      Averías reales. Lee el síntoma, elige la causa que creas y mira cómo se arregló.

      1. #1

        «Failed to connect: disconnected»

        29-10-2026 · Bloqueante: el servidor aparecía en la lista, pero nadie podía entrar

        Desde fuera el servidor salía en la lista con su número de jugadores, pero al entrar el juego decía «Failed to connect to the server: disconnected». En la red de casa, con bots, todo iba bien.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        El contenedor nuevo de la DMZ heredó el DNS de casa como primero. Desde la DMZ no se puede llegar a él (bien hecho), así que cada consulta esperaba 5 s antes de probar el siguiente. La lista de servidores no pregunta a Mojang, pero entrar sí: llegaba tarde y se cortaba.
        Solución
        DNS públicos en el contenedor de la DMZ, en Terraform (no a mano), y una nota en la documentación. Las pruebas de suplantación se repitieron: antes «pasaban» solo porque Mojang no respondía.
        Lección
        Una prueba que pasa por la razón equivocada es peor que una que falla. Hay que mirar por qué pasa.
      2. #2

        «No se puede verificar el mensaje»

        29-10-2026 · Media: un aviso en el chat de cada jugador

        Minecraft avisaba de que los mensajes del chat no se podían verificar, y algunos clientes los marcaban como inseguros.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        Detrás de un proxy, los servidores de juego no pueden comprobar las firmas del chat: quien verifica la cuenta es la entrada. El cliente veía un servidor sin chat seguro y lo avisaba.
        Solución
        La entrada le dice al cliente, en el paquete de login, que el servidor es online y exige chat seguro, y los fragmentos mandan el chat como mensajes del sistema (sin firma), igual que el que llega de otros fragmentos.
        Lección
        El cliente solo sabe lo que el servidor le cuenta: si en medio hay un proxy, tiene que contarlo bien.
      3. #3

        Vender un diamante tardaba 3 segundos

        27-10-2026 · Alta: con 10 jugadores vendiendo a la vez

        Con diez jugadores vendiendo a la vez, cada venta tardaba de mediana 2,8 s y algunas casi 5. La base de datos apenas trabajaba.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        La venta abría una transacción, bloqueaba el precio del material (para no vender de más ni de menos) y hacía varios viajes de 80 ms antes del COMMIT. Diez ventas a la vez = diez en fila.
        Solución
        Toda la venta en una sola sentencia SQL y el COMMIT en el mismo envío: el cerrojo dura un viaje en vez de cinco. De 2.768 ms a 852 de mediana, y nadie vende de más ni de menos.
        Lección
        Con una base lejos, lo que cuesta no son las consultas: son los viajes, sobre todo con un cerrojo puesto.
      4. #4

        El fragmento que tardó 40 minutos en morir

        22-10-2026 · Alta: un fragmento colgado en plena prueba

        Con 100 bots explorando terreno nuevo, un fragmento se quedó sin memoria y tardó 40 minutos en apagarse.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        Generar chunks nuevos es lo más caro de Minecraft. Sin límite por jugador, cien exploradores llenaban la cola de generación más rápido de lo que se vaciaba.
        Solución
        Un límite de generación por jugador (−20 % de CPU, 0 bots echados) y, después, un generador propio en Rust con la misma semilla: 2,35 veces más rápido que Folia sola en la misma máquina.
        Lección
        Antes de dar más memoria, hay que ver qué se acumula y por qué.
      5. #5

        Los menús lentos con una consulta de 1 ms

        23-10-2026 · Media: cada menú tardaba 115-270 ms en abrirse

        Abrir menús que leen de la base (casas, perfil…) tardaba hasta 270 ms, aunque la consulta tardaba menos de 1,5 ms.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        En Folia cada región tiene su hilo. La consulta iba en otro hilo y la respuesta tenía que esperar su turno en el de la región del jugador.
        Solución
        Lo que solo se enseña (casas, cachés de presentación) vive en memoria; lo que da o quita algo se sigue comprobando en la base. De 115-270 ms a 17-19.
        Lección
        Medir dónde se va el tiempo de verdad: la consulta era lo único que no hacía falta optimizar.

      Cómo se construyó

      1. 20-22 oct 2026

        Un mundo, varios servidores

        • Entrada propia en Rust con el empalme sin pantalla de carga.
        • Mando con el reparto por celdas y agentes que arrancan y paran fragmentos.
        • Almacén del mundo con zstd y diccionario.
      2. 22-25 oct 2026

        Rendimiento con datos

        • Generador de terreno en Rust con paridad del 99,3 % con la misma semilla.
        • Hilos de región, mobs y anti-xray medidos uno a uno.
        • Wiki de resultados: cada cambio, con su línea base y su decisión.
      3. 25-28 oct 2026

        Dos continentes

        • Una segunda zona a 80 ms, con su propio mando, réplica de PostgreSQL y paso de una a otra.
        • Bucle de optimización: juntar viajes a la base y medir cada orden con 100 bots.
        • Alta disponibilidad: si cae el mando, otro toma el control y vuelve junto a su base.
      4. 29 oct 2026

        Abierto al público

        • Entrada en la DMZ, solo cuentas de Mojang y auditoría de suplantación.
        • Una sola zona en producción y nada en mi PC.
        • Panel nuevo: servidores con una orden, plazas de toda la red y anuncios.

      Decisiones (y por qué)

      • Velocity o una entrada propia

        Entrada propia en Rust

        Para pasar de un servidor a otro sin pantalla de carga hay que empalmar los paquetes al vuelo, y eso un proxy genérico no lo hace.

      • Paper, Purpur o Folia

        Folia, con parches propios

        Folia reparte el mundo en regiones con su propio hilo: con muchos jugadores separados aprovecha todos los núcleos.

      • Redis delante de PostgreSQL

        No: menos viajes

        33 horas de pg_stat_statements: lo que pesaba eran escrituras que tienen que persistir. Juntar viajes ganó más que una caché.

      • Repartir el mundo a mano o solo

        Lo reparte el mando

        Por celdas, sin partir nunca una zona con gente (bases y redstone intactas) y con reasignación sola si un servidor cae.

      • Exponer el mando o solo la entrada

        Solo la entrada, en la DMZ

        La base de datos no se ve desde internet y los servicios hablan entre sí con certificados mutuos (mTLS).

      Lo que aprendí

      • Medir antes de tocar: la mitad de las ideas que parecían buenas no mejoraban nada y se descartaron con datos.
      • Con una base lejos, el enemigo son los viajes, no las consultas.
      • Una prueba que pasa por la razón equivocada es peor que una que falla.
      • Probar con dos continentes desde el principio obligó a que todo funcionara sin trucos locales.

      Lo siguiente

      • Más jugadores reales en las pruebas externas.
      • Sacar la IA de los mobs a un servicio aparte sin bajar los TPS.
      • Una segunda zona en producción cuando haya hierro para ello.
      • La versión 1.0.0, con el visto bueno de los que juegan.

      ¿Hablamos?

      Puedo contar cualquiera de estas decisiones en detalle en una entrevista.

      Descargar CV Contactar ← Volver al rack

      Reconstrucción a partir de la documentación y la wiki de resultados del proyecto: sin direcciones, puertos ni nombres internos.