Skip to main content

Escalabilidad y multitenencia

Diseñe la implementación del SDK de Copilot para atender a varios usuarios, controlar sesiones simultáneas y escalar horizontalmente en toda la infraestructura. En esta guía se tratan los patrones de aislamiento de sesión, las topologías de escalado y los procedimientos recomendados de producción.

Lo mejor para: Desarrolladores de plataformas, generadores de SaaS, cualquier implementación que sirva más de un puñado de usuarios simultáneos.

Conceptos principales

Antes de elegir un patrón, comprenda tres dimensiones de escalado:

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Patrones de aislamiento de sesión

Patrón 1: CLI aislada por usuario

Cada usuario obtiene su propia instancia del servidor de la CLI. Aislamiento más seguro: las sesiones, la memoria y los procesos de un usuario están completamente separados.

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Cuándo usar:

  • SaaS multiinquilino donde el aislamiento de datos es crítico
  • Usuarios con credenciales de autenticación diferentes
  • Requisitos de cumplimiento (SOC 2, HIPAA)
// CLI pool manager — one CLI per user
class CLIPool {
    private instances = new Map<string, { client: CopilotClient; port: number }>();
    private nextPort = 5000;

    async getClientForUser(userId: string, token?: string): Promise<CopilotClient> {
        if (this.instances.has(userId)) {
            return this.instances.get(userId)!.client;
        }

        const port = this.nextPort++;

        // Spawn a dedicated CLI for this user
        await spawnCLI(port, token);

        const client = new CopilotClient({
            cliUrl: `localhost:${port}`,
        });

        this.instances.set(userId, { client, port });
        return client;
    }

    async releaseUser(userId: string): Promise<void> {
        const instance = this.instances.get(userId);
        if (instance) {
            await instance.client.stop();
            this.instances.delete(userId);
        }
    }
}

Patrón 2: CLI compartida con aislamiento de sesión

Varios usuarios comparten un servidor de la CLI, pero tienen sesiones aisladas a través de identificadores de sesión únicos. Consume menos recursos, pero ofrece un aislamiento más débil.

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Cuándo usar:

  • Herramientas internas con usuarios de confianza
  • Entornos con restricción de recursos
  • Requisitos de aislamiento más bajos
const sharedClient = new CopilotClient({
    cliUrl: "localhost:4321",
});

// Enforce session isolation through naming conventions
function getSessionId(userId: string, purpose: string): string {
    return `${userId}-${purpose}-${Date.now()}`;
}

// Access control: ensure users can only access their own sessions
async function resumeSessionWithAuth(
    sessionId: string,
    currentUserId: string
): Promise<Session> {
    const [sessionUserId] = sessionId.split("-");
    if (sessionUserId !== currentUserId) {
        throw new Error("Access denied: session belongs to another user");
    }
    return sharedClient.resumeSession(sessionId);
}

Patrón 3: sesiones compartidas (colaborativas)

Varios usuarios interactúan con la misma sesión, como un salón de chat compartido con Copilot.

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Cuándo usar:

  • Herramientas de colaboración en equipo
  • Sesiones de revisión de código compartido
  • Asistentes de programación de pares

⚠️Importante: El SDK no proporciona bloqueo de sesión integrado. Debe serializar el acceso para evitar escrituras simultáneas en la misma sesión.

import Redis from "ioredis";

const redis = new Redis();

async function withSessionLock<T>(
    sessionId: string,
    fn: () => Promise<T>,
    timeoutSec = 300
): Promise<T> {
    const lockKey = `session-lock:${sessionId}`;
    const lockId = crypto.randomUUID();

    // Acquire lock
    const acquired = await redis.set(lockKey, lockId, "NX", "EX", timeoutSec);
    if (!acquired) {
        throw new Error("Session is in use by another user");
    }

    try {
        return await fn();
    } finally {
        // Release lock (only if we still own it)
        const currentLock = await redis.get(lockKey);
        if (currentLock === lockId) {
            await redis.del(lockKey);
        }
    }
}

// Usage: serialize access to shared session
app.post("/team-chat", authMiddleware, async (req, res) => {
    const result = await withSessionLock("team-project-review", async () => {
        const session = await client.resumeSession("team-project-review");
        return session.sendAndWait({ prompt: req.body.message });
    });

    res.json({ content: result?.data.content });
});

Comparación de patrones de aislamiento

CLI independiente para cada usuarioCli compartida + Aislamiento de sesiónSesiones compartidas
Isolation
✅ Completo
⚠️ Lógico
❌ Compartido
Uso de recursosAlto (CLI por usuario)Bajo (una CLI)Bajo (una CLI + sesión)
ComplejidadMedioBajoAlto (bloqueado)
Flexibilidad de autenticación
✅ Tokens por usuario
⚠️ Token de servicio
⚠️ Token de servicio
Mejor paraSaaS multiclienteHerramientas internasColaboración

Escalado horizontal

Varios servidores de la CLI (Interfaz de Línea de Comandos) detrás de un equilibrador de carga

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Requisito clave: El estado de sesión debe estar en el almacenamiento compartido para que cualquier servidor de la CLI pueda reanudar cualquier sesión.

// Route sessions to CLI servers
class CLILoadBalancer {
    private servers: string[];
    private currentIndex = 0;

    constructor(servers: string[]) {
        this.servers = servers;
    }

    // Round-robin selection
    getNextServer(): string {
        const server = this.servers[this.currentIndex];
        this.currentIndex = (this.currentIndex + 1) % this.servers.length;
        return server;
    }

    // Sticky sessions: same user always hits same server
    getServerForUser(userId: string): string {
        const hash = this.hashCode(userId);
        return this.servers[hash % this.servers.length];
    }

    private hashCode(str: string): number {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = (hash << 5) - hash + str.charCodeAt(i);
            hash |= 0;
        }
        return Math.abs(hash);
    }
}

const lb = new CLILoadBalancer([
    "cli-1:4321",
    "cli-2:4321",
    "cli-3:4321",
]);

app.post("/chat", async (req, res) => {
    const server = lb.getServerForUser(req.user.id);
    const client = new CopilotClient({ cliUrl: server });

    const session = await client.createSession({
        sessionId: `user-${req.user.id}-chat`,
        model: "gpt-4.1",
    });

    const response = await session.sendAndWait({ prompt: req.body.message });
    res.json({ content: response?.data.content });
});

Sesiones persistentes y almacenamiento compartido

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Las sesiones permanentes son más sencillas: anclar usuarios a servidores específicos de la CLI. No se necesita almacenamiento compartido, pero la distribución de carga es desigual.

El almacenamiento compartido permite que cualquier CLI controle cualquier sesión. Mejor distribución de carga, pero requiere almacenamiento en red para ~/.copilot/session-state/.

Escalado vertical

Ajuste de un único servidor de la CLI

Un único servidor de la CLI puede controlar muchas sesiones simultáneas. Consideraciones principales:

Diagrama: Diagrama de flujo que muestra el proceso descrito.

La administración del ciclo de vida de la sesión es clave para el escalado vertical:

// Limit concurrent active sessions
class SessionManager {
    private activeSessions = new Map<string, Session>();
    private maxConcurrent: number;

    constructor(maxConcurrent = 50) {
        this.maxConcurrent = maxConcurrent;
    }

    async getSession(sessionId: string): Promise<Session> {
        // Return existing active session
        if (this.activeSessions.has(sessionId)) {
            return this.activeSessions.get(sessionId)!;
        }

        // Enforce concurrency limit
        if (this.activeSessions.size >= this.maxConcurrent) {
            await this.evictOldestSession();
        }

        // Create or resume
        const session = await client.createSession({
            sessionId,
            model: "gpt-4.1",
        });

        this.activeSessions.set(sessionId, session);
        return session;
    }

    private async evictOldestSession(): Promise<void> {
        const [oldestId] = this.activeSessions.keys();
        const session = this.activeSessions.get(oldestId)!;
        // Session state is persisted automatically — safe to disconnect
        await session.disconnect();
        this.activeSessions.delete(oldestId);
    }
}

Sesiones efímeras frente a persistentes

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Sesiones efímeras

Para los puntos de conexión de API sin estado en los que cada solicitud es independiente:

app.post("/api/analyze", async (req, res) => {
    const session = await client.createSession({
        model: "gpt-4.1",
    });

    try {
        const response = await session.sendAndWait({
            prompt: req.body.prompt,
        });
        res.json({ result: response?.data.content });
    } finally {
        await session.disconnect();  // Clean up immediately
    }
});

Sesiones persistentes

Para interfaces conversacionales o flujos de trabajo de larga duración:

// Create a resumable session
app.post("/api/chat/start", async (req, res) => {
    const sessionId = `user-${req.user.id}-${Date.now()}`;

    const session = await client.createSession({
        sessionId,
        model: "gpt-4.1",
        infiniteSessions: {
            enabled: true,
            backgroundCompactionThreshold: 0.80,
        },
    });

    res.json({ sessionId });
});

// Continue the conversation
app.post("/api/chat/message", async (req, res) => {
    const session = await client.resumeSession(req.body.sessionId);
    const response = await session.sendAndWait({ prompt: req.body.message });

    res.json({ content: response?.data.content });
});

// Clean up when done
app.post("/api/chat/end", async (req, res) => {
    await client.deleteSession(req.body.sessionId);
    res.json({ success: true });
});

Despliegues de contenedores

Kubernetes con almacenamiento persistente

apiVersion: apps/v1
kind: Deployment
metadata:
  name: copilot-cli
spec:
  replicas: 3
  selector:
    matchLabels:
      app: copilot-cli
  template:
    metadata:
      labels:
        app: copilot-cli
    spec:
      containers:
        - name: copilot-cli
          image: your-registry/copilot-cli:latest  # See backend-services.md for how to build and push this image
          args: ["--headless", "--host", "0.0.0.0", "--port", "4321"]
          env:
            - name: COPILOT_GITHUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: copilot-secrets
                  key: github-token
          ports:
            - containerPort: 4321
          volumeMounts:
            - name: session-state
              mountPath: /root/.copilot/session-state
      volumes:
        - name: session-state
          persistentVolumeClaim:
            claimName: copilot-sessions-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: copilot-cli
spec:
  selector:
    app: copilot-cli
  ports:
    - port: 4321
      targetPort: 4321

Diagrama: Diagrama de flujo que muestra el proceso descrito.

Instancias de Contenedores de Azure (Azure Container Instances)

containers:
  - name: copilot-cli
    image: your-registry/copilot-cli:latest  # See backend-services.md for how to build and push this image
    command: ["copilot", "--headless", "--host", "0.0.0.0", "--port", "4321"]
    volumeMounts:
      - name: session-storage
        mountPath: /root/.copilot/session-state

volumes:
  - name: session-storage
    azureFile:
      shareName: copilot-sessions
      storageAccountName: myaccount

Lista de comprobación de envío

Diagrama: Diagrama de flujo que muestra el proceso descrito.

PreocupaciónRecomendación
Limpieza de sesiónEjecute una limpieza periódica para eliminar las sesiones que hayan superado su TTL
Comprobaciones de estadoHacer ping al servidor de la CLI periódicamente; reiniciar si no responde
StorageMontar volúmenes persistentes para ~/.copilot/session-state/
SecretosUsa el gestor de secretos de tu plataforma (Vault, K8s Secrets, etc.)
MonitoringSeguimiento del recuento de sesiones activas, latencia de respuesta, tasas de error
LockingUso de Redis o similar para el acceso a sesión compartida
ApagadoPurgar sesiones activas antes de detener los servidores de la CLI

Limitaciones

LimitaciónDetalles
Sin bloqueo de sesión integradoImplementación del bloqueo de nivel de aplicación para el acceso simultáneo
Sin equilibrio de carga integradoUsar un balanceador de carga externo o una malla de servicios
El estado de sesión está basado en archivosRequiere un sistema de archivos compartido para las configuraciones de varios servidores
Tiempo de espera de inactividad de 30 minutosLas sesiones sin actividad se limpian automáticamente mediante la CLI
La CLI es un proceso únicoAmplíe la capacidad añadiendo más instancias del servidor CLI, no subprocesos

Pasos siguientes