CodeGym /Cursos /ChatGPT Apps /Escalado y despliegue: balanceo, clústeres de servicios b...

Escalado y despliegue: balanceo, clústeres de servicios backend, blue/green y canary

ChatGPT Apps
Nivel 16 , Lección 3
Disponible

1. De qué trata esta lección y por qué es importante

Imagina que dejaste GiftGenius en la fase en la que vive en solitario en Vercel: una instancia del MCP‑gateway (que a la vez expone MCP hacia fuera y llama a tus servicios REST), un backend para los agentes, todo «más o menos funciona». Esto todavía es tolerable para un pet‑project y los primeros 100 usuarios.

Pero en cuanto OpenAI añada tu App al Store y de repente aparezca en la selección principal antes de Navidad, «un solo gateway en el puerto 3000» se convertirá en una historia muy triste: cola de llamadas de tools, timeouts, errores 500, caída de la valoración en el Store y correos de marketing del estilo «¿por qué estuvo todo caído en el pico de ventas?».

Nuestra tarea en esta lección es aprender a pensar en GiftGenius (y en cualquier ChatGPT App) como en un sistema de muchas instancias idénticas detrás de un balanceador. Además, entender estrategias de lanzamiento cuidadosas y un esquema claro de «cómo revertir si algo ha salido mal».

2. Escalado horizontal y diseño stateless

Empecemos con la idea básica: si tu MCP Gateway o un servicio backend interno guarda estado importante en la memoria de un proceso concreto, es prácticamente imposible escalarlo en horizontal de forma adecuada.

Escalado vertical vs horizontal

Primero, aclaremos la terminología.

Escalado vertical — es cuando simplemente «pones más músculo» a un único servidor: más CPU, más RAM. Es rápido, a veces barato al principio, pero tiene un límite duro y convierte a una sola instancia en single point of failure: si ese monstruo potente cae, cae todo.

Escalado horizontal — es cuando ejecutas varias réplicas del servicio detrás de un balanceador. Cada instancia es relativamente pequeña, no guarda nada crítico en memoria, y el estado se almacena en sistemas externos (Postgres, Redis, object storage). Puedes añadir y quitar instancias libremente según la carga.

Para el MCP Gateway y los servicios backend (Gift REST API, Commerce REST API, Analytics Service / REST API, etc.) el escalado horizontal es prácticamente obligatorio: ChatGPT puede enviarte de repente mucho más tráfico (temporada, promo en el Store, algún TikTok viral), y debes simplemente añadir instancias, no «rezar para que un único servidor aguante».

Qué es un servicio stateless en el contexto de MCP Gateway y los backends

Para que el escalado horizontal funcione, el servicio debe ser lo más stateless posible.

Stateless en nuestro contexto significa:

  • el servicio no guarda en memoria estado único y de larga duración del usuario del que dependa la lógica de negocio;
  • cualquier estado importante vive en una BD externa, en una cola, en una caché o en un almacenamiento tipo S3;
  • si una instancia concreta cae, otra puede seguir atendiendo al usuario, simplemente «recogiendo» el contexto del almacenamiento externo.

Para GiftGenius esto implica que:

  • el historial de selecciones de regalos del usuario, sus likes/dislikes y el carrito están, por ejemplo, en Postgres;
  • las colas de tareas largas (generación masiva de selecciones, envío de selecciones por correo) residen en un broker tipo Redis/Cloud Queue;
  • si hay un servicio separado para workflows de agentes complejos, guarda checkpoints y memoria de larga duración en su propio store, no en la RAM de un proceso.

Una instancia del MCP Gateway o de cualquier servicio backend se convierte en «ganado, no mascotas»: se puede matar y recrear sin piedad sin perder datos de negocio.

Mini‑ejemplo: mover el estado de la memoria a un almacén externo

Imagina que en algún momento hiciste un MCP‑tool muy simple, add_to_cart, que a través del gateway llama a la lógica interna y esta guarda el carrito en la memoria del proceso (sí, a veces se hace en demos — y está bien mientras entiendas que en producción no se debe):

// MALO: la cesta en la memoria del proceso del servicio backend
const inMemoryCarts = new Map<string, string[]>();

export async function addToCart(userId: string, sku: string) {
  const cart = inMemoryCarts.get(userId) ?? [];
  cart.push(sku);
  inMemoryCarts.set(userId, cart);
  return cart;
}

Aquí el escalado horizontal es imposible: una solicitud irá a la instancia A, otra a la instancia B, y el usuario tendrá carritos diferentes.

La opción correcta es mover el carrito a una BD externa o a una caché. Algo así (muy simplificado):

// BIEN: el carrito en un almacén externo
import { db } from "./db";

export async function addToCart(userId: string, sku: string) {
  await db.cartItems.insert({ userId, sku }); // simplificado
  const cart = await db.cartItems.findMany({ where: { userId } });
  return cart;
}

Ahora da igual qué instancia del servicio backend procesa la solicitud llegada a través del gateway: el carrito es único para todos.

3. Balanceo de carga: cómo el tráfico llega a los clústeres de servicios backend

En cuanto tengas más de una instancia de un servicio, necesitas algo que reparta las solicitudes entre ellas. Es como el repartidor de pedidos en una pizzería popular: hay muchos repartidores, muchos clientes, y sin lógica — caos.

L4 vs L7, y por qué nos interesa sobre todo L7

Un balanceador puede operar en distintos niveles:

  • L4 (TCP/UDP) simplemente envía bytes del cliente a uno de los backends, sin entender demasiado el protocolo;
  • L7 (HTTP) entiende que delante tiene una solicitud HTTP, puede mirar la ruta, cabeceras, cookies e incluso a veces el cuerpo.

Para la arquitectura de ChatGPT App con MCP Gateway y servicios REST, casi siempre necesitamos un balanceador L7: todo habla HTTP/SSE, y queremos poder enrutar por ruta, dominio, cabeceras (por ejemplo, para canary‑releases) y hacer health checks.

Health checks y retirar de la rotación las instancias «enfermas»

El balanceador debe comprobar periódicamente que las instancias están vivas. La manera más simple es tener un endpoint GET /health o /readyz que devuelva 200 OK cuando todo está bien.

En un servicio Node/TypeScript que actúa como MCP Gateway o backend, el health check puede tener este aspecto:

// apps/gateway/src/http/health.ts
import { type Request, type Response } from "express";

export function healthHandler(req: Request, res: Response) {
  res.json({
    status: "ok",
    version: process.env.RELEASE_ID ?? "dev",
  });
}

El balanceador llama cada N segundos a /health. Si las respuestas comienzan a salir con 5xx o por timeout, esa instancia se excluye de la rotación y el tráfico nuevo deja de enviarse allí.

Particularidades para streaming / SSE

El MCP Gateway a menudo trabaja con SSE (Server‑Sent Events), especialmente si usas streaming de resultados parciales. El balanceador debe:

  • soportar conexiones HTTP de larga duración;
  • saber contabilizar este tipo de conexiones al elegir instancia (algunos LB pueden tener en cuenta el número de conexiones activas, no solo el RPS).

Esto es importante porque una llamada de tool que «habla mucho» y transmite texto durante 2 minutos, cuelga como una conexión activa. Si hay demasiadas conexiones así en una instancia, hay que «descargarla» temporalmente — enviar conexiones nuevas a otras.

4. Clústeres de servicios backend: separar por tareas, no todo en el mismo saco

El siguiente paso lógico es dejar de pensar en un único «gran servicio backend» y dividir el sistema en varios clústeres según el tipo de carga y su criticidad.

Ejemplo de arquitectura de GiftGenius por clústeres

Todo lo recopilado en el módulo 16 nos sugiere este esquema para GiftGenius:

Clúster Qué hace Perfil de carga Particularidades de escalado
A: Gift REST API / herramientas ligeras Búsqueda de productos, formateo de listas, cálculos simples RPS alto, respuestas cortas (< 500 ms), poco uso de CPU Escalamos por CPU/RPS, muchas instancias pequeñas
B: Agents / servicio REST de trabajos pesados Llamadas a LLM, workflows complejos, generación de felicitaciones RPS bajo, respuestas largas (10 s–2 min), intensivo en E/S Escalamos según la longitud de la cola de tareas; se pueden usar workers
C: Commerce REST API / ACP Checkout, integración con el proveedor de pagos, ACP Fiabilidad crítica, SLO exigentes Despliegue separado, cambios lentos y prudentes

En esencia, es la implementación del patrón bulkheads (compartimentos): si el clúster B de repente empieza a «quemar CPU con tokens» al generar textos complejos, el clúster C de pagos seguirá funcionando, porque tiene su propio pool de recursos y su propio escalado.

Cómo se ve a través del Gateway

El MCP Gateway, descrito en la primera lección del módulo, ve todo el tráfico MCP entrante y lo enruta a los clústeres backend. Aproximadamente así:

  • llamadas de tools list_gifts, suggest_gifts → clúster A (Gift REST API);
  • llamadas de tools generate_greeting_card o workflows de agentes complejos → clúster B (servicio REST de Agents o workers);
  • herramientas create_order, confirm_payment → clúster C (Commerce REST API).

Detrás de esto puede haber un balanceador común o varios balanceadores (por ejemplo, un L7‑LB separado delante de commerce para aislar aún más).

Podemos dibujar el panorama general:

flowchart LR
    ChatGPT((ChatGPT))
    GW[MCP Gateway]
    LBA[LB Gift API Cluster A]
    LBB[LB Agents/Workers Cluster B]
    LBC[LB Commerce API Cluster C]

    A1[Gift REST API A-1]
    A2[Gift REST API A-2]
    B1[Agents Service B-1]
    B2[Agents Service B-2]
    C1[Commerce REST API C-1]
    C2[Commerce REST API C-2]

    ChatGPT --> GW
    GW -->|tools: gifts| LBA
    GW -->|agents workflows| LBB
    GW -->|commerce| LBC

    LBA --> A1
    LBA --> A2
    LBB --> B1
    LBB --> B2
    LBC --> C1
    LBC --> C2

El esquema está algo idealizado, pero refleja el principio clave: distintos tipos de carga — distintos clústeres backend detrás de un único MCP Gateway.

5. Estrategias de despliegue: por qué hacen falta blue/green y canary

Pasemos ahora a cómo actualizar todo esto para que los usuarios no lo noten y tú puedas dormir tranquilo por las noches.

Anti‑ejemplo: desplegar «por encima del entorno de producción»

La estrategia más simple y más peligrosa: coges el clúster en producción (por ejemplo, el clúster Gift REST API A), lanzas la nueva imagen encima de la antigua, sustituyes contenedores o reinicias procesos.

Problemas:

  • mientras parte de las instancias ya son nuevas y parte antiguas, el sistema puede comportarse de forma impredecible (especialmente si el esquema de la BD cambió);
  • si algo va mal, el rollback es un nuevo despliegue «como estaba», que puede tardar minutos;
  • en el momento del despliegue puedes tener un breve downtime cuando ninguna instancia está aún arriba.

En Kubernetes y PaaS esto se mitiga un poco con rolling updates, pero la idea general es la misma: sin una estrategia clara, tienes una gran «zona gris» en la que distintas versiones del código procesan tráfico a la vez.

Despliegue blue/green: dos entornos y conmutación instantánea

Blue/Green es un enfoque en el que tienes dos entornos casi idénticos al mismo tiempo: Blue (producción actual) y Green (la versión nueva).

Esquemáticamente, el proceso es así:

  1. Despliegas la nueva versión (v2) en el entorno Green: es el mismo conjunto de gateway + clústeres backend, pero aún sin tráfico real.
  2. Ejecutas en Green todas las pruebas necesarias: tests automáticos, smoke tests, verificaciones manuales a través de ChatGPT Dev Mode.
  3. En el momento del release, cambias el balanceador/enrutamiento de forma que el 100% del tráfico real vaya a Green.
  4. Blue sigue viviendo al lado como «pista de aterrizaje de reserva». Si algo sale mal, conmutas de vuelta en cuestión de segundos.

Para GiftGenius podría verse así: tienes mcp-gateway-blue.example.com y mcp-gateway-green.example.com. La ChatGPT App en producción «mira» al endpoint MCP oficial (gateway), y en el release cambias la configuración de DNS/LB para que el nombre de dominio mcp-gateway.example.com apunte ya a green.

Ventajas:

  • conmutación instantánea de un lado a otro;
  • cualquier problema se puede arreglar después del rollback;
  • no existe el estado «medio clúster nuevo, medio clúster antiguo».

Inconvenientes:

Durante el release necesitas mantener dos entornos completos, es decir, pagar recursos ×2. Por eso esta estrategia suele aplicarse a servicios backend críticos — por ejemplo, el clúster de commerce C y el propio MCP Gateway, donde romper el checkout y el punto de entrada no es aceptable bajo ninguna circunstancia.

Lanzamientos canary: la pequeña «canaria» en la mina

El canary es una opción más económica: no levantas dos producciones completas, sino que despliegas la nueva versión de forma gradual a una pequeña fracción del tráfico y la vigilas atentamente.

Escenario aproximado:

  1. Despliegas la versión v2 del clúster Gift REST API A en el mismo pool o en un pequeño pool canary separado.
  2. Configuras el balanceador o el MCP Gateway para que, por ejemplo, el 1% de las llamadas de tools relacionadas con regalos vaya a v2, y el 99% — a v1.
  3. Observas métricas: tasa de errores, latencia, métricas de negocio específicas (conversión, checkouts exitosos).
  4. Si todo va bien — aumentas gradualmente la fracción: 1% → 5% → 10% → 50% → 100%. Si va mal — haces rollback urgentemente.

En el contexto de ChatGPT Apps, canary es especialmente útil no solo para código, sino también para experimentar con prompts: una versión nueva del system‑prompt para el servicio de agentes puede cambiar radicalmente el comportamiento, y es mejor probarla primero con una muestra pequeña de usuarios.

El Gateway o el LB pueden decidir qué solicitud es «canaria» según distintos criterios:

  • al azar (por ejemplo, el 1% de todas las solicitudes);
  • por userId (una parte de usuarios entra al experimento para siempre);
  • por una cabecera o cookie especial (para pruebas internas).

Pequeño ejemplo de lógica de enrutado en TypeScript (pseudocódigo, para ilustrar la idea en el gateway):

// Pseudocódigo en el Gateway: canary aleatorio simple 5%
function routeToGiftBackendCluster(ctx: { userId?: string | null }) {
  const rnd = Math.random();
  if (rnd < 0.05) {
    return "gift-api-v2"; // canary
  }
  return "gift-api-v1";   // stable
}

En la vida real, por supuesto, no harás esto con Math.random() en código de runtime, sino que llevarás las reglas a configuración/feature flags, pero la lógica es muy similar: una parte del tráfico va a versiones canary del servicio backend y el resto a la estable.

6. El rollback como parte obligatoria de la estrategia

Hace tiempo aprendí una buena regla: el rollback debe ser más rápido que el fix.

Esto significa que si después del release empiezan a caer errores y los usuarios escriben «todo se rompe», no hace falta arreglar heroicamente el bug en producción. Hay que pulsar el gran botón rojo de «rollback».

En plataformas como Vercel (donde ya desplegamos la parte Next.js de GiftGenius) esto es muy natural: cada despliegue es un artefacto inmutable y Vercel permite volver rápidamente al anterior.

Para el MCP Gateway y los clústeres backend desplegados en Kubernetes u otro orquestador, esta función la cumple kubectl rollout undo: vuelves a la versión anterior de pods e imágenes.

Lo principal es registrar en logs y mostrar la versión que está sirviendo tráfico ahora. Por ejemplo, se puede:

  • añadir version a /health y a otros endpoints de diagnóstico (ya lo hicimos arriba);
  • propagar el identificador de la release a través de cabeceras en los logs (por ejemplo, X-Release-Id).

Mini‑ejemplo: una ruta de API de Next.js que devuelve la versión de la build para inspección del ChatGPT App dentro del widget:

// apps/web/app/api/version/route.ts
export async function GET() {
  return Response.json({
    version: process.env.RELEASE_ID ?? "dev",
    builtAt: process.env.BUILT_AT ?? "unknown",
  });
}

Este endpoint también es útil para depuración: puedes preguntar a la instancia en producción qué versión está corriendo exactamente y no preguntarte «¿se desplegó de verdad la última build?».

7. Capacity planning: cuántas instancias necesita GiftGenius

Ya hemos hablado de cómo lanzar versiones nuevas de forma segura (blue/green, canary) y hacer rollback rápido ante problemas. Queda una cuestión práctica: ¿cuántas instancias y de qué clústeres mantener en producción para soportar tráfico real sin arruinarte?

Sin fanatismo de fórmulas, pero un poco sí. El escalado debe relacionarse con la carga y la economía: cuántas solicitudes al día/segundo, cuántas llamadas pesadas a LLM, cuánto cuesta al día.

Para simplificar, piensa en órdenes de magnitud:

  • con 10k solicitudes al día a GiftGenius (aprox. 0.1 RPS de media) vivirás sin problema con una o dos instancias del MCP Gateway y un par de instancias del Gift REST API/workers de Agents;
  • con 100k solicitudes al día (12 RPS de media, con picos mayores) ya conviene tener 35 instancias del gateway + del clúster Gift REST API, un clúster B separado para agentes pesados y un clúster de commerce dedicado;
  • con 1M solicitudes al día (decenas de RPS, cargas pico en festivos) necesitarás sin duda clústeres, recursos dedicados para agentes LLM, una caché agresiva y una capa edge (hay una lección aparte).

No son cifras estrictas, sino una forma de obligarte a estimar el orden de la carga y pensar por adelantado: dónde están los cuellos de botella, cómo vas a escalar y cuánto costará.

Para GiftGenius es especialmente importante prepararse para las fiestas: Año Nuevo, Navidad, San Valentín, Black Friday. La carga puede multiplicarse, y querrás que el sistema lo soporte.

8. Mini ejemplo práctico: evolución del despliegue de GiftGenius

Para unirlo todo, dibujemos una evolución simple del despliegue de GiftGenius.
Aquí aplicaremos secuencialmente todo lo que hemos comentado: diseño stateless del gateway y de los servicios backend, balanceo de carga, clústeres separados y estrategias de lanzamiento (blue/green, canary).

Nivel básico: un gateway + backend en Vercel/Kubernetes

En algún punto del curso ya hiciste esto: una aplicación Next.js con Apps SDK en Vercel, dentro de la cual vive el endpoint MCP y lógica backend sencilla (Gift/Commerce) en un único servicio. Todo bastante monolítico.

Las ventajas son claras: simple, barato, pocos lugares donde equivocarse.

La desventaja es única pero crítica: esto no escala para tráfico serio y lleva mal las actualizaciones.

Nivel 2: MCP Gateway separado + varios clústeres backend

Siguiente paso:

  • extraes el MCP Gateway a un servicio separado (Node/Go/NGINX+Lua, da igual);
  • levantas varias instancias del Gift REST API (clúster A) y varios workers/servicios para los agentes (clúster B);
  • para commerce dedicas un servicio separado (clúster C), posiblemente con su propia base/infraestructura.

Aquí ya entra en juego el balanceo L7 clásico, los health checks y, en la medida de lo posible, el escalado horizontal.

Nivel 3: estrategias de lanzamiento

En este nivel añades:

  • Blue/Green para el clúster de commerce C (y, si quieres, para el MCP Gateway), para que el checkout y la autenticación sean lo más estables posible;
  • lanzamientos Canary para los clústeres Gift REST API y el servicio de agentes, para experimentar con versiones nuevas de tools y agentes sin riesgo de tumbar toda la producción.

Esquemáticamente:

flowchart LR
    ChatGPT((ChatGPT))
    GWBlue[Gateway Blue]
    GWGreen[Gateway Green]
    LB[Traffic Switch]

    subgraph Prod
      LB --> GWBlue
      LB -.canary,% .-> GWGreen
    end

    ChatGPT --> LB

En la realidad puede ser un poco más complejo (Blue/Green solo para commerce, canary solo para los clústeres de gift), pero la idea queda ilustrada: siempre sabes qué versión va a dónde, y para ChatGPT sigue pareciendo un único punto de entrada MCP (gateway).

9. Pequeños fragmentos de código para versionado y diagnóstico

Ya vimos el health endpoint y /api/version. Añadamos otro ejemplo de cómo registrar versión y clúster en el handler de un MCP‑tool en el lado del gateway, para luego «cuadrar» métricas fácilmente.

Imaginemos el tool suggest_gifts, implementado como endpoint REST en el Gift REST API y llamado a través del gateway:

import { type McpToolHandler } from "@modelcontextprotocol/sdk";

export const suggestGifts: McpToolHandler<{
  occasion: string;
  budget: number;
}> = async ({ input, meta }) => {
  const releaseId = process.env.RELEASE_ID ?? "dev";
  const clusterId = process.env.CLUSTER_ID ?? "gift-api-A";

  console.log("[suggest_gifts]", {
    releaseId,
    clusterId,
    userId: meta.userId,
    occasion: input.occasion,
  });

  // aquí el MCP Gateway llama al Gift REST API según la tabla de enrutamiento,
  // y la herramienta en sí sigue siendo un contenedor fino sobre la llamada REST
  return {
    content: [{ type: "text", text: "Gift ideas..." }],
  };
};

Aquí:

  • leemos RELEASE_ID y CLUSTER_ID de las variables de entorno;
  • las escribimos en logs estructurados;
  • luego es fácil usarlas para analizar: «¿en qué versión/clúster tenemos ahora más errores?».

Desde el punto de vista de la ChatGPT App esto es transparente, pero para ti como desarrollador — una gran ventaja, especialmente combinado con canary/blue‑green.

10. Errores típicos al escalar y desplegar una ChatGPT App

Error n.º 1: guardar estado de sesión/usuario en la memoria del proceso del gateway o del backend.
Este enfoque mata el escalado horizontal: en cuanto aparece una segunda instancia, el estado «se estratifica» entre ellas. Es especialmente peligroso guardar en memoria el carrito, resultados de búsqueda o el progreso de un workflow. Todo esto debe vivir en un almacenamiento externo — BD, caché o un store especializado para el estado del agente.

Error n.º 2: pensar que «un servidor potente» es suficiente.
El escalado vertical es cómodo al inicio, pero funciona mal con crecimiento real: hay un límite físico de la máquina, un proceso se convierte en single point of failure, y ChatGPT puede traer picos de tráfico impredecibles. Para el MCP Gateway y los clústeres backend casi siempre se necesita un diseño stateless y varias instancias detrás de un balanceador.

Error n.º 3: lanzar versiones nuevas «encima de producción» sin estrategia clara.
Si simplemente actualizas contenedores/procesos en el clúster de producción, obtienes un estado intermedio en el que parte del tráfico va a la versión antigua y parte a la nueva, y ante un error el rollback se convierte en «volver a desplegar otra vez». Es mucho más fiable mantener dos entornos (blue/green) o al menos una versión canary del servicio backend a la que vaya una pequeña fracción del tráfico.

Error n.º 4: ausencia de un plan de rollback rápido.
Mal escenario: el release salió, las métricas están en rojo, los usuarios se quejan, y tú apenas empiezas a pensar cómo revertir. Escenario correcto: capacidad de rollback instantáneo preparada de antemano (conmutador blue/green, rollout undo, Vercel rollback), identificadores de versión claros en logs y health endpoints, y la regla estricta «rollback primero, investigar después».

Error n.º 5: un único clúster «para todo» sin separar por tipos de carga.
Si la generación de textos de felicitación (agentes LLM) y el checkout viven en el mismo clúster, cualquier problema del lado de los modelos (latencias, timeouts, crecimiento de tokens) puede tumbar también los pagos. Separar por clústeres según el tipo de tareas (Gift REST API / herramientas ligeras, servicio pesado de Agents, Commerce REST API) y poner límites/recursos separados para cada clúster es un paso importante hacia la resiliencia.

Error n.º 6: no conectar arquitectura y economía.
Es fácil entusiasmarse con la idea de «levantar un par de nodos más», olvidando que cada llamada a LLM y cada instancia cuestan dinero. Sin un mínimo capacity planning (estimación de cargas y coste) puedes quedarte corto y tumbar producción, o sobredimensionarte y perder margen. Es útil relacionar número de solicitudes, porcentaje de operaciones LLM pesadas y coste de hosting con las métricas de negocio de la aplicación.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION