CodeGym /Cursos /ChatGPT Apps /Escalonamento e deploy: balanceamento, clusters de serviç...

Escalonamento e deploy: balanceamento, clusters de serviços de backend, blue/green e canary

ChatGPT Apps
Nível 16 , Lição 3
Disponível

1. Sobre o que é esta aula e por que isso é importante

Imagine que você deixou o GiftGenius naquele estágio em que ele vive sozinho no Vercel: uma instância do MCP‑gateway (que ao mesmo tempo expõe o MCP para fora e chama seus serviços REST), um backend para agents — “meio que funciona”. Isso ainda é tolerável para um pet project e os primeiros 100 usuários.

Mas assim que a OpenAI adicionar seu App na Store e ele de repente aparecer em destaque antes do Natal, “um gateway na porta 3000” vira uma história bem triste: fila de chamadas de tools, timeouts, erros 500, queda de classificação na Store e emails do marketing no estilo “por que tudo ficou fora do ar no pico de vendas?”.

Nosso objetivo nesta aula é aprender a pensar no GiftGenius (e em qualquer ChatGPT App) como um sistema de várias instâncias idênticas atrás de um balanceador. Além disso, vamos entender estratégias de release cuidadosas e um esquema claro de “como voltar atrás se algo der errado”.

2. Escalonamento horizontal e design stateless

Vamos começar com a ideia básica: se seu MCP Gateway ou um serviço de backend interno armazena estado importante na memória de um processo específico, é praticamente impossível escalá‑lo horizontalmente de forma adequada.

Escalonamento vertical vs horizontal

Primeiro, vamos alinhar a terminologia.

Escalonamento vertical é quando você simplesmente “bomba” um único servidor: mais CPU, mais RAM. É rápido, às vezes barato no início, mas tem um limite rígido e transforma uma instância em single point of failure: se esse monstro poderoso cair, tudo cai.

Escalonamento horizontal é quando você executa várias réplicas do serviço atrás de um balanceador. Cada instância é relativamente pequena, não guarda nada crítico em memória, e o estado vai para armazenamentos externos (Postgres, Redis, object storage). Você pode adicionar e remover instâncias conforme a carga.

Para o MCP Gateway e serviços de backend (Gift REST API, Commerce REST API, Analytics Service / REST API etc.), o escalonamento horizontal é praticamente obrigatório: o ChatGPT pode, de repente, enviar muito mais tráfego (sazonalidade, promo na Store, algum TikTok viral), e você deve apenas adicionar instâncias — não “rezar para um único servidor aguentar”.

O que é um serviço stateless no contexto de MCP Gateway e backends

Para que o escalonamento horizontal funcione, o serviço deve ser o mais stateless possível.

Stateless no nosso contexto significa:

  • o serviço não mantém em memória estado único e de longa duração do usuário do qual depende a lógica de negócio;
  • qualquer estado importante fica em um banco externo, fila, cache ou armazenamento do tipo S3;
  • se uma instância cair, outra pode continuar atendendo o usuário, simplesmente “puxando” o contexto do armazenamento externo.

Para o GiftGenius, isso significa que:

  • o histórico de seleção de presentes do usuário, seus likes/dislikes e o carrinho ficam, por exemplo, no Postgres;
  • as filas de tarefas longas (geração em massa de listas, disparo de emails com seleções) ficam em um broker como Redis/Cloud Queue;
  • se houver um serviço separado para workflows complexos de agents, ele armazena checkpoints e memória de longa duração em seu próprio store, não na RAM de um único processo.

A instância do MCP Gateway ou de qualquer serviço de backend vira “gado, não bichinho de estimação”: pode ser encerrada e recriada sem piedade, sem perder dados de negócio.

Mini‑exemplo: mover o estado da memória para um armazenamento externo

Suponha que você tenha feito um MCP‑tool bem simples, add_to_cart, que via gateway chama a lógica interna e esta guarda o carrinho na memória do processo (sim, às vezes fazem isso em demos — e tudo bem, desde que você saiba que não vale para produção):

// RUIN: carrinho na memória do processo do serviço de 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;
}

Aqui o escalonamento horizontal é impossível: uma requisição vai para a instância A, outra para a instância B, e o usuário terá carrinhos diferentes.

A opção correta é colocar o carrinho em um banco externo ou cache. De forma (bem) simplificada:

// BOM: carrinho em armazenamento 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;
}

Agora não importa qual instância do serviço de backend atende a requisição que veio pelo gateway: o carrinho é único para todos.

3. Balanceamento de carga: como o tráfego chega aos clusters de serviços de backend

Assim que você tem mais de uma instância do serviço, é preciso alguém para distribuir as requisições entre elas. É como o distribuidor de pedidos em uma pizzaria popular: há muitos entregadores e muitos clientes — sem lógica, vira caos.

L4 vs L7 e por que o que mais nos interessa é L7

O balanceador pode atuar em diferentes camadas:

  • L4 (TCP/UDP) apenas encaminha bytes do cliente para um dos backends, sem entender muito o protocolo;
  • L7 (HTTP) entende que está diante de uma requisição HTTP, consegue olhar o path, headers, cookies, às vezes até o corpo.

Para a arquitetura de ChatGPT App com MCP Gateway e serviços REST, quase sempre precisamos de um balanceador L7: tudo se comunica por HTTP/SSE, e queremos rotear por caminho, domínio, headers (por exemplo, para canary releases) e fazer health‑checks.

Health‑checks e remoção de instâncias “doentes” da rotação

O balanceador deve verificar periodicamente se as instâncias estão vivas. A forma mais simples é ter um endpoint GET /health ou /readyz que retorna 200 OK quando tudo está bem.

Em um serviço Node/TypeScript que funciona como MCP Gateway ou backend, o health‑check pode ser assim:

// 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",
  });
}

O balanceador bate a cada N segundos em /health. Se as respostas começam a sair com 5xx ou por timeout, essa instância é retirada da rotação e tráfego novo não é enviado para lá.

Particularidades para Streaming / SSE

O MCP Gateway frequentemente trabalha com SSE (Server‑Sent Events), especialmente se você usa streaming de resultados parciais. O balanceador deve:

  • suportar conexões HTTP de longa duração;
  • conseguir contar essas conexões ao escolher a instância (alguns LBs consideram a quantidade de conexões ativas, não apenas RPS).

Isso é importante porque uma chamada de tool “falante”, que faz streaming de texto por 2 minutos, fica como conexão ativa. Se houver conexões demais em uma instância, é preciso “descarregá‑la” temporariamente — enviando novas conexões para outras.

4. Clusters de serviços de backend: separe por tarefa, não coloque tudo no mesmo lugar

O próximo passo lógico é parar de pensar em um “grande serviço de backend” e dividir o sistema em vários clusters conforme o perfil de carga e a criticidade.

Exemplo de arquitetura do GiftGenius por clusters

Todos os dados reunidos no módulo 16 recomendam o seguinte esquema para o GiftGenius:

Cluster O que faz Perfil de carga Características de escalonamento
A: Gift REST API / ferramentas leves Busca de produtos, formatação de listas, cálculos simples RPS alto, respostas curtas (< 500 ms), pouco CPU Escalonar por CPU/RPS, muitas instâncias pequenas
B: Agents / serviço REST para jobs pesados Chamadas LLM, workflows complexos, geração de felicitações RPS baixo, respostas longas (10s–2min), pesado em IO Escalonar pelo tamanho da fila de tarefas, pode usar workers
C: Commerce REST API / ACP Checkout, integração com o provedor de pagamentos, ACP Confiabilidade crítica, SLO rígidos Deploy separado, mudanças lentas e cautelosas

Na prática, é a implementação do padrão bulkheads (compartimentos): se o cluster B de repente começa a “queimar CPU em tokens” ao gerar textos complexos, o cluster C de pagamentos continua funcionando, porque tem seu próprio pool de recursos e seu próprio escalonamento.

Como isso aparece via Gateway

O MCP Gateway, descrito na primeira aula do módulo, vê todo o tráfego MCP de entrada e o roteia para os clusters de backend. Aproximadamente assim:

  • chamadas de tool list_gifts, suggest_gifts → cluster A (Gift REST API);
  • chamadas de tool generate_greeting_card ou workflows complexos de agents → cluster B (serviço REST de Agents ou workers);
  • ferramentas create_order, confirm_payment → cluster C (Commerce REST API).

Por trás disso pode haver um balanceador único ou vários (por exemplo, um L7‑LB separado na frente de commerce para isolar ainda mais).

Podemos ilustrar a visão geral:

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

O diagrama é um pouco idealizado, mas reflete o princípio principal: diferentes tipos de carga — diferentes clusters de backend atrás de um único MCP Gateway.

5. Estratégias de deploy: para que servem blue/green e canary

Agora vamos ver como atualizar tudo isso sem que os usuários percebam — e para que você durma tranquilo à noite.

Anti‑exemplo: deploy “por cima da produção”

A estratégia mais simples e mais perigosa: você pega o cluster em produção (por exemplo, o cluster Gift REST API A), sobe a nova imagem por cima da antiga, substitui contêineres ou reinicia processos.

Quais são os problemas aqui:

  • enquanto parte das instâncias já é nova e parte é antiga, o sistema pode se comportar de forma imprevisível (especialmente se o schema do banco mudou);
  • se algo sair errado, o rollback é um novo deploy “como era”, que pode levar minutos;
  • no momento do deploy, é bem possível ter um pequeno downtime, quando nenhuma instância ainda subiu.

No Kubernetes e em PaaS isso melhora um pouco com atualizações do tipo rolling, mas a ideia geral é a mesma: sem uma estratégia clara, você tem uma grande “zona cinzenta”, onde versões diferentes do código processam tráfego ao mesmo tempo.

Deploy blue/green: dois ambientes e comutação instantânea

Blue/Green é uma abordagem em que você mantém ao mesmo tempo dois ambientes quase idênticos: Blue (produção atual) e Green (nova versão).

O processo, esquematicamente, é assim:

  1. Você provisiona a nova versão (v2) no ambiente Green: é o mesmo conjunto de gateway + clusters de backend, só que ainda sem tráfego real.
  2. Roda no Green todos os testes necessários: automatizados, smoke, verificações manuais via ChatGPT Dev Mode.
  3. No momento do release, muda o balanceador/roteamento para que 100% do tráfego de produção vá para o Green.
  4. O Blue continua vivo ao lado como “pista de pouso reserva”. Se algo der errado, você volta o tráfego em segundos.

Para o GiftGenius, isso pode ser assim: você tem mcp-gateway-blue.example.com e mcp-gateway-green.example.com. O ChatGPT App em produção aponta para o endpoint MCP oficial (gateway) e, no release, você altera a configuração de DNS/LB para que o domínio mcp-gateway.example.com aponte para o green.

Prós:

  • comutação instantânea “de um lado para o outro”;
  • qualquer problema pode ser tratado após o rollback;
  • não há estado “meio cluster novo, meio cluster antigo”.

Contras:

Durante o release, é preciso manter dois ambientes completos, ou seja, pagar recursos ×2. Por isso, essa estratégia é mais comum para serviços de backend críticos — por exemplo, o cluster de commerce (C) e o próprio MCP Gateway, onde não se pode quebrar o checkout nem a porta de entrada sob nenhuma hipótese.

Releases canary: uma pequena “canária” na mina de carvão

Um release canary é uma opção mais econômica: você não levanta duas produções inteiras, mas libera a nova versão aos poucos para uma pequena parcela do tráfego e observa atentamente.

Roteiro aproximado:

  1. Você faz deploy da versão v2 do cluster Gift REST API A no mesmo pool ou em um pequeno pool canário separado.
  2. Configura o balanceador ou o MCP Gateway para que, digamos, 1% das chamadas de tools relacionadas a presentes vá para a v2, e 99% para a v1.
  3. Monitora as métricas: taxa de erro, latência, métricas de negócio específicas (conversão, checkouts bem‑sucedidos).
  4. Se estiver tudo bem, aumenta gradualmente a fatia: 1% → 5% → 10% → 50% → 100%. Se não estiver, faz rollback imediatamente.

No contexto de ChatGPT Apps, canary é especialmente útil não só para código, mas também para experimentos com prompts: uma nova versão do system prompt para o serviço de agents pode mudar radicalmente o comportamento — melhor testar antes com um subconjunto pequeno de usuários.

O Gateway ou o LB pode decidir o que é “canário” por vários critérios:

  • aleatoriamente (por exemplo, 1% de todas as requisições);
  • por userId (parte dos usuários cai no experimento permanentemente);
  • por um header ou cookie especial (para testes internos).

Um pequeno exemplo de lógica de roteamento em pseudo‑TypeScript (apenas para ilustrar a ideia no gateway):

// Pseudocódigo no Gateway: canary aleatório simples de 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
}

Na prática, claro, você não fará isso via Math.random() no código de runtime e sim colocará as regras em config/feature flags, mas a lógica é bem parecida: uma parte do tráfego vai para versões canário do serviço de backend, o restante para a estável.

6. Rollback como parte obrigatória da estratégia

Há muito tempo eu aprendi uma boa regra: o rollback deve ser mais rápido do que o fix.

Isso significa que, se depois do release choverem erros e usuários disserem “tudo está quebrado”, não é para sair consertando heroicamen­te em produção. É para apertar o grande botão vermelho “voltar atrás”.

Em plataformas como o Vercel (onde já fizemos o deploy da parte Next.js do GiftGenius), isso é bem natural: cada deploy é um artefato imutável, e o Vercel permite voltar rapidamente para o anterior.

Para o MCP Gateway e clusters de backend em Kubernetes ou outro orquestrador, esse papel é do kubectl rollout undo: você volta para o conjunto anterior de pods e imagens.

O principal é registrar e expor a versão que está atendendo o tráfego. Por exemplo, você pode:

  • adicionar version ao /health e outros endpoints de diagnóstico (já fizemos acima);
  • propagar o identificador do release via headers nos logs (por exemplo, X-Release-Id).

Mini‑exemplo: uma route de API do Next.js que retorna a versão do build para inspeção do ChatGPT App dentro de um 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",
  });
}

Esse endpoint também é útil para debug: você pode perguntar à instância de produção qual versão está rodando exatamente — sem adivinhar “será que o último build foi mesmo para o ar?”.

7. Capacity planning: quantas instâncias são necessárias para o GiftGenius

Já falamos sobre como liberar novas versões com segurança (blue/green, canary) e voltar rapidamente em caso de problemas. Fica a pergunta prática: afinal, quantas instâncias e quais clusters manter em produção para aguentar tráfego real sem estourar os custos?

Sem exagerar nas fórmulas, mas com um pouco de rigor. O escalonamento precisa estar ligado à carga e à economia: quantas requisições por dia/segundo, quantas chamadas LLM pesadas, quanto isso custa em dinheiro.

Para simplificar, pense em ordens de grandeza:

  • com 10k requisições por dia no GiftGenius (cerca de 0.1 RPS em média), você vive tranquilamente com uma ou duas instâncias do MCP Gateway e um par de instâncias do Gift REST API/workers de Agents;
  • com 100k requisições por dia (12 RPS em média, picos maiores), já vale ter 35 instâncias do gateway + cluster do Gift REST API, um cluster B separado para agents pesados e um cluster de commerce dedicado;
  • com 1M requisições por dia (dezenas de RPS, picos em feriados), você certamente vai precisar de clusters, recursos dedicados para agents LLM, cache agressivo e uma camada de edge (tema de outra aula).

Não são números exatos, e sim uma forma de se obrigar a estimar a carga de forma aproximada e pensar com antecedência: onde estão os gargalos, como você vai escalar e quanto isso vai custar.

Para o GiftGenius, é especialmente importante se preparar para as datas sazonais: Ano‑Novo, Natal, Dia dos Namorados, Black Friday. A carga pode multiplicar, e você quer que o sistema aguente.

8. Mini‑exemplo prático: a evolução do deploy do GiftGenius

Para juntar tudo, vamos desenhar uma evolução simples do deploy do GiftGenius.
Aqui aplicaremos, passo a passo, tudo o que discutimos acima: design stateless do gateway e dos serviços de backend, balanceamento de carga, clusters separados e estratégias de release (blue/green, canary).

Nível básico: um gateway + backend no Vercel/Kubernetes

Em algum momento do curso você já fez isso: um aplicativo Next.js com Apps SDK no Vercel, onde vivem o endpoint MCP e uma lógica de backend simples (Gift/Commerce) em um único serviço. Tudo bem monolítico.

Os prós são claros: simples, barato, poucos lugares para errar.

O contra é único, mas crítico: isso não escala para tráfego sério e lida mal com atualizações.

Nível 2: MCP Gateway separado + vários clusters de backend

O próximo passo:

  • separar o MCP Gateway em um serviço próprio (Node/Go/NGINX+Lua, tanto faz);
  • executar várias instâncias do Gift REST API (cluster A) e vários workers/serviços para agents (cluster B);
  • isolar commerce em um serviço separado (cluster C), possivelmente com banco/infra próprios.

Aqui já entram o balanceamento clássico L7, health‑checks e, se possível, escalonamento horizontal.

Nível 3: Estratégias de release

Neste nível você adiciona:

  • Blue/Green para o cluster de commerce C (e, se quiser, para o MCP Gateway), para que checkout e autenticação sejam o mais estáveis possível;
  • releases canary para os clusters do Gift REST API e do serviço de agents, para experimentar com novas versões de tools e agents sem risco de derrubar toda a produção.

Esquematicamente:

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

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

    ChatGPT --> LB

Na prática, pode ser um pouco mais complexo (Blue/Green apenas para commerce, canary só para os clusters de gift), mas a ideia é essa: você sempre sabe qual versão vai para onde, enquanto para o ChatGPT continua parecendo um único ponto de entrada MCP (gateway).

9. Pequenos trechos de código para versionamento e diagnóstico

Já vimos o health‑endpoint e o /api/version. Vamos adicionar mais um exemplo de como registrar versão e cluster no handler de um MCP‑tool no gateway, para depois cruzar métricas com facilidade.

Imagine o tool suggest_gifts, implementado como um endpoint REST no Gift REST API e chamado via 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,
  });

  // aqui o MCP Gateway, pela tabela de roteamento, chama o Gift REST API,
  // e o próprio tool permanece uma fina camada por cima da chamada REST
  return {
    content: [{ type: "text", text: "Gift ideas..." }],
  };
};

Aqui nós:

  • lemos RELEASE_ID e CLUSTER_ID das variáveis de ambiente;
  • gravamos nos logs estruturados;
  • depois fica fácil analisar: “em qual versão/cluster estamos tendo mais erros agora?”.

Do ponto de vista do ChatGPT App, isso é transparente, mas para você como desenvolvedor é uma grande vantagem — sobretudo combinado com canary/blue‑green.

10. Erros comuns ao escalar e fazer deploy de um ChatGPT App

Erro nº 1: manter o estado de sessão/usuário na memória do processo do gateway ou backend.
Essa abordagem mata o escalonamento horizontal: assim que surge uma segunda instância, o estado “se estratifica” entre elas. É especialmente perigoso manter em memória o carrinho, resultados de busca ou progresso de workflow. Tudo isso deve viver em armazenamento externo — banco, cache ou store especializado para estado de agent.

Erro nº 2: achar que “um servidor potente” é suficiente.
Escalonar verticalmente é conveniente no início, mas funciona mal com crescimento real: há limite físico da máquina, um processo vira single point of failure, e o ChatGPT pode trazer picos imprevisíveis. Para MCP Gateway e clusters de backend quase sempre é preciso design stateless e várias instâncias atrás de um balanceador.

Erro nº 3: liberar novas versões “por cima da produção” sem estratégia clara.
Se você simplesmente atualiza contêineres/processos no cluster de produção, cria um estado intermediário em que parte do tráfego vai para a versão antiga e parte para a nova — e, em caso de erro, o rollback vira “fazer deploy de novo”. Muito mais confiável é manter dois ambientes (blue/green) ou pelo menos uma versão canary do serviço de backend para a qual vai uma pequena fração do tráfego.

Erro nº 4: não ter um plano de rollback rápido.
Cenário ruim: o release foi, as métricas estão em vermelho, os usuários reclamam — e você começa a pensar em como voltar atrás. Cenário correto: possibilidade de rollback instantâneo previamente preparada (comutação blue/green, rollout undo, rollback no Vercel), identificadores de versão claros nos logs e health‑endpoints, e a regra rígida “voltar primeiro, investigar depois”.

Erro nº 5: um único cluster “para tudo” sem separação por tipo de carga.
Se a geração de textos de felicitações (agents LLM) e o checkout vivem no mesmo cluster, qualquer problema no lado dos modelos (atrasos, timeouts, aumento de tokens) pode derrubar também os pagamentos. Separar em clusters por tipo de tarefa (Gift REST API / ferramentas leves, serviço pesado de Agents, Commerce REST API) com limites/recursos próprios para cada um é um passo importante rumo à resiliência.

Erro nº 6: desconectar arquitetura de economia.
É fácil se empolgar com “vamos subir mais alguns nós” e esquecer que cada chamada LLM e cada instância custam dinheiro. Sem um mínimo de capacity planning (estimativa de cargas e custos), você pode subescalar e derrubar a produção, ou superescalar e perder margem. É útil relacionar número de requisições, percentual de operações LLM pesadas e custo de hosting com as métricas de negócio do aplicativo.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION