Skip to main content

Mise à l’échelle et multilocation

Concevez votre déploiement Copilot SDK pour servir plusieurs utilisateurs, gérer des sessions simultanées et effectuer une mise à l’échelle horizontalement sur l’ensemble de l’infrastructure. Ce guide traite des modèles d’isolation de session, des topologies de mise à l’échelle et des meilleures pratiques de production.

Meilleur pour : Développeurs de plateforme, générateurs SaaS, tout déploiement servant plus d’un petit nombre d’utilisateurs simultanés.

Concepts de base

Avant de choisir un modèle, comprenez trois dimensions de la mise à l’échelle :

Diagramme : Organigramme montrant le processus décrit.

Modèles d’isolation de session

Modèle 1 : interface CLI isolée par utilisateur

Chaque utilisateur obtient sa propre instance de serveur CLI. Isolation la plus forte : les sessions, la mémoire et les processus d’un utilisateur sont complètement séparés.

Diagramme : Organigramme montrant le processus décrit.

Quand utiliser :

  • SaaS multilocataire où l’isolation des données est critique
  • Utilisateurs disposant d’identifiants d’authentification différents
  • Exigences de conformité (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);
        }
    }
}

Modèle 2 : interface CLI partagée avec isolation de session

Plusieurs utilisateurs partagent un serveur CLI, mais ont des sessions isolées via des ID de session uniques. Moins gourmand en ressources, mais isolation moindre.

Diagramme : Organigramme montrant le processus décrit.

Quand utiliser :

  • Outils internes avec des utilisateurs approuvés
  • Environnements contraints par des ressources
  • Exigences d’isolation inférieures
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);
}

Modèle 3 : sessions partagées (collaboratives)

Plusieurs utilisateurs interagissent avec la même session, comme une salle de conversation partagée avec Copilot.

Diagramme : Organigramme montrant le processus décrit.

Quand utiliser :

  • Outils de collaboration d’équipe
  • Sessions de révision de code partagé
  • Associer des assistants de programmation

⚠️Important: Le Kit de développement logiciel (SDK) ne fournit pas de verrouillage de session intégré. Vous devez sérialiser l’accès pour empêcher les écritures simultanées dans la même session.

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 });
});

Comparaison des modèles d’isolation

Interface CLI isolée par utilisateurInterface CLI partagée + isolation de sessionSessions partagées
Isolation
✅ Terminé
⚠️ Logique
❌ Partagé
Utilisation des ressourcesHigh (CLI par utilisateur)Faible (une interface CLI)Faible (une seule session CLI)
ComplexitéMoyenneFaibleÉlevé (verrouillage)
Flexibilité de l’authentification
✅ Jetons par utilisateur
⚠️ Jeton de service
⚠️ Jeton de service
Idéal pourSaaS multilocataireOutils internesCollaboration

Scalabilité horizontale

Plusieurs serveurs CLI derrière un équilibreur de charge

Diagramme : Organigramme montrant le processus décrit.

Condition clé : L’état de session doit être sur le stockage partagé afin que n’importe quel serveur CLI puisse reprendre n’importe quelle session.

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

Sessions sticky et stockage partagé

Diagramme : Organigramme montrant le processus décrit.

Les sessions persistantes sont plus simples : affectez les utilisateurs à des serveurs CLI spécifiques. Aucun stockage partagé n’est nécessaire, mais la distribution de charge n’est pas inégale.

Le stockage partagé permet à n’importe quelle interface CLI de gérer n’importe quelle session. Meilleure distribution de charge, mais nécessite un stockage en réseau pour ~/.copilot/session-state/.

Scalabilité verticale

Réglage d’un serveur CLI unique

Un serveur CLI unique peut gérer de nombreuses sessions simultanées. Considérations clés :

Diagramme : Organigramme montrant le processus décrit.

La gestion du cycle de vie des sessions est essentielle à la mise à l’échelle verticale :

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

Sessions éphémères et persistantes

Diagramme : Organigramme montrant le processus décrit.

Sessions éphémères

Pour les points de terminaison d’API sans état où chaque requête est indépendante :

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
    }
});

Sessions persistantes

Pour les interfaces conversationnelles ou les workflows de longue durée :

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

Déploiements de conteneurs

Kubernetes avec stockage persistant

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

Diagramme : Organigramme montrant le processus décrit.

Azure Container Instances (Instances de Conteneur Azure)

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

Liste de contrôle de production

Diagramme : Organigramme montrant le processus décrit.

PréoccupationRecommandation
Nettoyage de sessionExécutez régulièrement un nettoyage pour supprimer les sessions plus anciennes que votre TTL
Vérifications d’intégritéEnvoyez régulièrement une requête ping au serveur CLI ; redémarrez-le s’il ne répond pas.
StockageMonter des volumes persistants pour ~/.copilot/session-state/
SecretsUtilisez le gestionnaire de secrets de votre plateforme (Coffre, K8s Secrets, etc.)
MonitoringSuivre le nombre de sessions actives, la latence de réponse, les taux d’erreur
LockingUtiliser Redis ou similaire pour l’accès à une session partagée
ArrêterLaisser les sessions actives se terminer avant d’arrêter les serveurs CLI

Limitations

LimitationDétails
Aucun verrouillage de session intégréImplémenter le verrouillage au niveau de l’application pour l’accès simultané
Aucun équilibrage de charge intégréUtiliser un LB externe ou un maillage de service
L’état de session est basé sur des fichiersNécessite un système de fichiers partagé pour les configurations multiserveurs
Délai d’inactivité de 30 minutesLes sessions sans activité sont nettoyées automatiquement par l’interface CLI
L’interface CLI est un processus uniqueFaites évoluer en ajoutant davantage d’instances du serveur CLI, pas de threads

Étapes suivantes