Skip to content

Escalado y alta disponibilidad

MCP REST Bridge corre feliz como un único proceso — una instancia Bun con un fichero SQLite local maneja mucho. Cuando necesitas redundancia o más throughput, escala horizontalmente: ejecuta varias instancias idénticas tras un load balancer, coordinadas a través de una base de datos SQLite compartida.

El modelo

  • Manejo de requests stateless. Cada llamada de tool es autocontenida; cualquier instancia puede servir cualquier llamada REST.
  • SQLite es la capa de coordinación. Config admin, guards, keys, audit, uso y las primitivas HA viven todas en la base de datos. Apunta cada instancia al mismoDB_PATH (almacenamiento compartido / un volumen compartido) para que vean una sola config.
  • Flags de HA opt-in activan comportamiento cross-instancia (abajo) — están off por defecto para que un único nodo se mantenga simple.
Scaling MCP REST Bridge horizontally MCP clients reach a load balancer, which spreads traffic across several identical bridge instances. Every instance shares one SQLite database for config, cross-instance rate counters, registry sync and the leader lease. MCP clientsLoad balancerhealth-check /health · sticky MCP sessionsMCP REST Bridgeinstance 1MCP REST Bridgeinstance 2MCP REST Bridgeinstance 3Shared SQLiteconfig · rate counters · registry sync · leader lease
Identical instances behind a load balancer, coordinated through one shared SQLite. Each still proxies to your REST & MCP backends; background loops run on the elected leader only.

Activar las primitivas HA

SettingEfecto
RATE_LIMIT_SHARED=trueLos rate limits usan contadores fixed-window en SQLite, de modo que un límite por tool se aplica en todas las instancias, no por proceso.
REGISTRY_SYNC=trueCada instancia reconcilia periódicamente su registry en vivo desde SQLite — un cliente registrado (o eliminado) en un nodo se propaga a los otros.

Los loops en background que deben correr una vez — evaluación de alertas, schedules de mantenimiento y el loop de health-check/auto-eliminación — eligen un único líder automáticamente vía un lease en SQLite. Esto no es una flag; siempre está activo y no requiere configuración.

Load balancing de tus backends

Separado de escalar el bridge mismo, un único cliente puede fan-out entre varios targets de backend (load balancing N-way), configurado por cliente desde la admin API. Un target que falla se salta durante LB_TARGET_COOLDOWN_MS (por defecto 30s) antes de probarlo de nuevo. Combina con canary/failover por cliente (consulta Guardrails y resiliencia) para degradación con gracia.

Sesiones MCP y sticky routing

El transporte Streamable HTTP mantiene estado por sesión en memoria en la instancia que abrió la sesión. Dos opciones:

  • Sticky sessions — habilita afinidad de sesión en tu load balancer para los endpoints MCP (/mcp, /mcp/:name, /mcp-custom/:bundle) para que una sesión se quede en una instancia. Recomendado para clientes streaming.
  • Llamadas stateless — los clientes que abren un request fresco por llamada no necesitan afinidad y se balancean libremente.

El proxy REST y la admin API no necesitan afinidad.

Caveats que debes conocer

  • SQLite compartido requiere almacenamiento compartido. SQLite sobre un filesystem de red tiene quirks de locking; prefiere un volumen que todas las instancias monten localmente, o mantén las escrituras modestas. Para volumen de escritura muy alto, ejecuta menos instancias, más grandes.

  • Todas las guardas salvo el rate limiter cuentan por instancia. RATE_LIMIT_SHARED=true mueve los contadores de rate limit a SQLite; nada más se mueve. Los circuit breakers, la caché de respuestas, el coalescing de peticiones en vuelo y los cooldowns de destinos del balanceador viven en la memoria del proceso que vio el tráfico. Con N instancias, en concreto:

    GuardaEfecto con N instancias
    Circuit breakerUn backend caído tiene que fallar failureThreshold veces por instancia antes de que abran todas
    Caché de respuestasHasta N copias de la misma entrada; el hit rate baja aproximadamente N veces con tráfico repartido
    CoalescingColapsa duplicados concurrentes dentro de una instancia, no entre ellas
    Cooldown de destinoUn destino marcado como caído en una instancia lo siguen intentando las demás hasta que también fallan

    Ninguno es un bug de corrección: cada instancia llega a la misma decisión, solo que por su cuenta y con sus propias evidencias. Dimensiona CIRCUIT_BREAKER_FAILURE_THRESHOLD y los TTL de caché contando con el número de réplicas, y usa sticky sessions (arriba) si el hit rate de caché te importa.

  • La cadena de hash del audit es por instancia. Su tamper-evidence (verifyAuditChain) asume un único escritor; la integridad de la cadena cross-instancia está fuera de scope — streamea a un SIEM (AUDIT_SINK_URL) para un registro consolidado y ordenado en su lugar.

  • Un health-eviction hold también es por instancia, si enrutas con /livez en vez de /readyz. Bajo la topología por defecto (solo-líder, gateada por /readyz) esto no importa — solo el líder llega a probar backends o servir tráfico. Pero si cambias el readiness a /livez para que cada réplica sirva (consulta el override del readiness-probe del Helm chart →), un re-registro de cliente puede aterrizar en una réplica que no es el líder y solo limpiar la marca de eviction en memoria de esa réplica, dejando la propia marca del líder (y por tanto su reconcileFromDb()) atascada reteniendo el cliente. Vuelve a registrar contra el líder, o usa forgetClient/re-regístralo a través de una sesión anclada a él, para limpiar el hold.

Checklist

  • [ ] Todas las instancias comparten un DB_PATH
  • [ ] RATE_LIMIT_SHARED=true y REGISTRY_SYNC=true
  • [ ] El load balancer chequea /health (o /livez) para enrutar tráfico REST/MCP del plano de datos — el manejo de requests es stateless, así que cualquier instancia sirve sin importar el liderazgo. /readyz da 200 solo en el líder actual (leader lease + base de datos); apuntar ahí un LB de escalado por throughput saca de rotación a todas las demás instancias. Reserva el enrutado condicionado a /readyz para una topología deliberada de failover activo/pasivo, y usa /livez para el probe de liveness/reinicio del orquestador
  • [ ] Sticky sessions para los endpoints MCP /mcp · /mcp/:name · /mcp-custom/:bundle (si usas streaming)
  • [ ] AUDIT_SINK_URL configurado para un audit trail consolidado

Consulta Despliegue → para el setup del contenedor y Configuración → para cada flag.

Distribuido bajo la licencia MIT · Construido con Bun + Vue.