# スケーリングとマルチテナント

複数のユーザーにサービスを提供し、同時セッションを処理し、インフラストラクチャ全体で水平方向にスケーリングするように、Copilot SDK のデプロイを設計します。 このガイドでは、セッション分離パターン、スケーリング トポロジ、運用のベスト プラクティスについて説明します。

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

SDK レベルのオプションとパターンについては、 [マルチテナントとサーバーの展開](/ja/copilot/how-tos/copilot-sdk/setup/multi-tenancy) を参照してください。

**次の場合に最適です。** プラットフォーム開発者、SaaS ビルダー、少数を超える同時実行ユーザーにサービスを提供するデプロイ。

## 主要な概念

パターンを選択する前に、スケーリングの 3 つのディメンションを理解してください。

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png)

## セッション分離パターン

### パターン 1: ユーザーごとに分離された CLI

各ユーザーは、独自の CLI サーバー インスタンスを取得します。 最も強力な分離- ユーザーのセッション、メモリ、プロセスは完全に分離されます。

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-1.png)

**使用するタイミング:**

* データの分離が重要なマルチテナント SaaS
* 異なる認証資格情報を持つユーザー
* コンプライアンス要件 (SOC 2、HIPAA)

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

### パターン 2: セッション分離を使用した共有 CLI

複数のユーザーが 1 つの CLI サーバーを共有しますが、一意のセッション ID を介して分離されたセッションを持っています。 リソースは軽くなりますが、分離は弱くなります。

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-2.png)

**使用するタイミング:**

* 信頼されたユーザーを含む内部ツール
* リソースに制約のある環境
* 分離要件の低減

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

### パターン 3: 共有セッション (コラボレーション)

複数のユーザーが同じセッション (Copilot との共有チャット ルームなど) と対話します。

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-3.png)

**使用するタイミング:**

* チーム コラボレーション ツール
* 共同コード レビュー セッション
* ペアプログラミング支援アシスタント

> ⚠️**大事な：** SDK では、組み込みのセッション ロックは提供されません。 同じセッションへの同時書き込みを防止するには、アクセスをシリアル化する **必要があります** 。

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

## 分離パターンの比較

|               | ユーザーごとに分離された CLI | 共有 CLI + セッション分離 | 共有セッション              |
| ------------- | ---------------- | ---------------- | -------------------- |
| **Isolation** |                  |                  |                      |
| ✅ 完了          |                  |                  |                      |
| ⚠️ 論理的        |                  |                  |                      |
| ❌ 共有          |                  |                  |                      |
| **リソースの使用状況** | 高 (ユーザーあたりの CLI) | 低 (1 つの CLI)     | 低 (1 つの CLI + セッション) |
| **複雑さ**       | 中程度              | 低                | 高 (ロック)              |
| **認証の柔軟性**    |                  |                  |                      |
| ✅ ユーザーごとのトークン |                  |                  |                      |
| ⚠️ サービス トークン  |                  |                  |                      |
| ⚠️ サービス トークン  |                  |                  |                      |
| **最適な用途**     | マルチテナント SaaS     | 内部ツール            | コラボレーション             |

## 水平スケーリング

### ロード バランサーの背後にある複数の CLI サーバー

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-4.png)

**主な要件:** 任意の CLI サーバーが任意のセッションを再開できるように、セッション状態は **共有ストレージ** 上にある必要があります。

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

### スティッキー セッションと共有ストレージ

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-5.png)

**スティッキー セッション** の方が簡単で、ユーザーを特定の CLI サーバーにピン留めします。 共有ストレージは必要ありませんが、負荷分散は不均一です。

**共有ストレージ** を使用すると、任意の CLI で任意のセッションを処理できます。 負荷分散が向上しますが、 `~/.copilot/session-state/`にはネットワークストレージが必要です。

## 垂直スケーリング

### 1 つの CLI サーバーのチューニング

1 つの CLI サーバーで、多数の同時セッションを処理できます。 主な考慮事項:

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-6.png)

**セッション ライフサイクル管理** は、垂直スケーリングの鍵となります。

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

## 一時的セッションと永続的セッション

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-7.png)

### 一時的なセッション

各要求が独立しているステートレス API エンドポイントの場合:

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

### 永続的セッション

会話型インターフェイスまたは実行時間の長いワークフローの場合:

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

## コンテナーのデプロイ

### 永続ストレージ付きのKubernetes

```yaml
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
```

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-8.png)

### Azure Container Instances

```yaml
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
```

## 実稼働チェックリスト

![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-9.png)

| 懸念                                          | レコメンデーション                                         |
| ------------------------------------------- | ------------------------------------------------- |
| **セッションのクリーンアップ**                           | 定期的なクリーンアップを実行して TTL より古いセッションを削除する               |
| **ヘルスチェック**                                 | CLI サーバーに定期的に ping を実行します。応答しない場合は再起動する           |
| **Storage**                                 |                                                   |
| `~/.copilot/session-state/` の永続ボリュームをマウントする |                                                   |
| **シークレット**                                  | プラットフォームのシークレット マネージャー (Vault、K8s シークレットなど) を使用する |
| **Monitoring**                              | アクティブなセッション数、応答の待機時間、エラー率を追跡する                    |
| **Locking**                                 | 共有セッション アクセスに Redis または類似を使用する                    |
| **シャットダウン**                                 | CLI サーバーを停止する前にアクティブなセッションをドレインする                 |

## 制限事項

| 制限事項                    | 詳細情報                                   |
| ----------------------- | -------------------------------------- |
| **組み込みのセッション ロックなし**    | 同時実行アクセス用にアプリケーション レベルのロックを実装する        |
| **組み込みの負荷分散なし**         | 外部 LB またはサービスメッシュを使用する                 |
| **セッションの状態はファイル ベースです** | マルチサーバーセットアップ用の共有ファイルシステムが必要           |
| **30 分間の無操作タイムアウト**     | アクティビティのないセッションは CLI によって自動クリーンアップされます |
| **CLI は単一プロセスです**       | スレッドではなく CLI サーバー インスタンスを追加してスケーリングする  |

## 次のステップ

* **[セッションの再開と永続化](/ja/copilot/how-tos/copilot-sdk/features/session-persistence)**: 再開可能なセッションの詳細
* **[バックエンド サービスのセットアップ](/ja/copilot/how-tos/copilot-sdk/setup/backend-services)**: コア サーバー側のセットアップ
* **[GitHub OAuth のセットアップ](/ja/copilot/how-tos/copilot-sdk/setup/github-oauth)**: マルチユーザー認証
* **[BYOK (独自のキーを持ち込む)](/ja/copilot/how-tos/copilot-sdk/auth/byok)**: 独自のモデル プロバイダーを使用する