{"meta":{"title":"スケーリングとマルチテナント","intro":"複数のユーザーにサービスを提供し、同時セッションを処理し、インフラストラクチャ全体で水平方向にスケーリングするように、Copilot SDK のデプロイを設計します。 このガイドでは、セッション分離パターン、スケーリング トポロジ、運用のベスト プラクティスについて説明します。","product":"GitHub Copilot","breadcrumbs":[{"href":"/ja/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/ja/enterprise-cloud@latest/copilot/how-tos","title":"方法"},{"href":"/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/setup","title":"Copilot SDK を設定する"},{"href":"/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/setup/scaling","title":"スケーリング"}],"documentType":"article"},"body":"# スケーリングとマルチテナント\n\n複数のユーザーにサービスを提供し、同時セッションを処理し、インフラストラクチャ全体で水平方向にスケーリングするように、Copilot SDK のデプロイを設計します。 このガイドでは、セッション分離パターン、スケーリング トポロジ、運用のベスト プラクティスについて説明します。\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\nSDK レベルのオプションとパターンについては、 [マルチテナントとサーバーの展開](/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/setup/multi-tenancy) を参照してください。\n\n**次の場合に最適です。** プラットフォーム開発者、SaaS ビルダー、少数を超える同時実行ユーザーにサービスを提供するデプロイ。\n\n## 主要な概念\n\nパターンを選択する前に、スケーリングの 3 つのディメンションを理解してください。\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png)\n\n## セッション分離パターン\n\n### パターン 1: ユーザーごとに分離された CLI\n\n各ユーザーは、独自の CLI サーバー インスタンスを取得します。 最も強力な分離- ユーザーのセッション、メモリ、プロセスは完全に分離されます。\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-1.png)\n\n**使用するタイミング:**\n\n* データの分離が重要なマルチテナント SaaS\n* 異なる認証資格情報を持つユーザー\n* コンプライアンス要件 (SOC 2、HIPAA)\n\n```typescript\n// CLI pool manager — one CLI per user\nclass CLIPool {\n    private instances = new Map<string, { client: CopilotClient; port: number }>();\n    private nextPort = 5000;\n\n    async getClientForUser(userId: string, token?: string): Promise<CopilotClient> {\n        if (this.instances.has(userId)) {\n            return this.instances.get(userId)!.client;\n        }\n\n        const port = this.nextPort++;\n\n        // Spawn a dedicated CLI for this user\n        await spawnCLI(port, token);\n\n        const client = new CopilotClient({\n            cliUrl: `localhost:${port}`,\n        });\n\n        this.instances.set(userId, { client, port });\n        return client;\n    }\n\n    async releaseUser(userId: string): Promise<void> {\n        const instance = this.instances.get(userId);\n        if (instance) {\n            await instance.client.stop();\n            this.instances.delete(userId);\n        }\n    }\n}\n```\n\n### パターン 2: セッション分離を使用した共有 CLI\n\n複数のユーザーが 1 つの CLI サーバーを共有しますが、一意のセッション ID を介して分離されたセッションを持っています。 リソースは軽くなりますが、分離は弱くなります。\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-2.png)\n\n**使用するタイミング:**\n\n* 信頼されたユーザーを含む内部ツール\n* リソースに制約のある環境\n* 分離要件の低減\n\n```typescript\nconst sharedClient = new CopilotClient({\n    cliUrl: \"localhost:4321\",\n});\n\n// Enforce session isolation through naming conventions\nfunction getSessionId(userId: string, purpose: string): string {\n    return `${userId}-${purpose}-${Date.now()}`;\n}\n\n// Access control: ensure users can only access their own sessions\nasync function resumeSessionWithAuth(\n    sessionId: string,\n    currentUserId: string\n): Promise<Session> {\n    const [sessionUserId] = sessionId.split(\"-\");\n    if (sessionUserId !== currentUserId) {\n        throw new Error(\"Access denied: session belongs to another user\");\n    }\n    return sharedClient.resumeSession(sessionId);\n}\n```\n\n### パターン 3: 共有セッション (コラボレーション)\n\n複数のユーザーが同じセッション (Copilot との共有チャット ルームなど) と対話します。\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-3.png)\n\n**使用するタイミング:**\n\n* チーム コラボレーション ツール\n* 共同コード レビュー セッション\n* ペアプログラミング支援アシスタント\n\n> ⚠️**大事な：** SDK では、組み込みのセッション ロックは提供されません。 同じセッションへの同時書き込みを防止するには、アクセスをシリアル化する **必要があります** 。\n\n```typescript\nimport Redis from \"ioredis\";\n\nconst redis = new Redis();\n\nasync function withSessionLock<T>(\n    sessionId: string,\n    fn: () => Promise<T>,\n    timeoutSec = 300\n): Promise<T> {\n    const lockKey = `session-lock:${sessionId}`;\n    const lockId = crypto.randomUUID();\n\n    // Acquire lock\n    const acquired = await redis.set(lockKey, lockId, \"NX\", \"EX\", timeoutSec);\n    if (!acquired) {\n        throw new Error(\"Session is in use by another user\");\n    }\n\n    try {\n        return await fn();\n    } finally {\n        // Release lock (only if we still own it)\n        const currentLock = await redis.get(lockKey);\n        if (currentLock === lockId) {\n            await redis.del(lockKey);\n        }\n    }\n}\n\n// Usage: serialize access to shared session\napp.post(\"/team-chat\", authMiddleware, async (req, res) => {\n    const result = await withSessionLock(\"team-project-review\", async () => {\n        const session = await client.resumeSession(\"team-project-review\");\n        return session.sendAndWait({ prompt: req.body.message });\n    });\n\n    res.json({ content: result?.data.content });\n});\n```\n\n## 分離パターンの比較\n\n|               | ユーザーごとに分離された CLI | 共有 CLI + セッション分離 | 共有セッション              |\n| ------------- | ---------------- | ---------------- | -------------------- |\n| **Isolation** |                  |                  |                      |\n| ✅ 完了          |                  |                  |                      |\n| ⚠️ 論理的        |                  |                  |                      |\n| ❌ 共有          |                  |                  |                      |\n| **リソースの使用状況** | 高 (ユーザーあたりの CLI) | 低 (1 つの CLI)     | 低 (1 つの CLI + セッション) |\n| **複雑さ**       | 中程度              | 低                | 高 (ロック)              |\n| **認証の柔軟性**    |                  |                  |                      |\n| ✅ ユーザーごとのトークン |                  |                  |                      |\n| ⚠️ サービス トークン  |                  |                  |                      |\n| ⚠️ サービス トークン  |                  |                  |                      |\n| **最適な用途**     | マルチテナント SaaS     | 内部ツール            | コラボレーション             |\n\n## 水平スケーリング\n\n### ロード バランサーの背後にある複数の CLI サーバー\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-4.png)\n\n**主な要件:** 任意の CLI サーバーが任意のセッションを再開できるように、セッション状態は **共有ストレージ** 上にある必要があります。\n\n```typescript\n// Route sessions to CLI servers\nclass CLILoadBalancer {\n    private servers: string[];\n    private currentIndex = 0;\n\n    constructor(servers: string[]) {\n        this.servers = servers;\n    }\n\n    // Round-robin selection\n    getNextServer(): string {\n        const server = this.servers[this.currentIndex];\n        this.currentIndex = (this.currentIndex + 1) % this.servers.length;\n        return server;\n    }\n\n    // Sticky sessions: same user always hits same server\n    getServerForUser(userId: string): string {\n        const hash = this.hashCode(userId);\n        return this.servers[hash % this.servers.length];\n    }\n\n    private hashCode(str: string): number {\n        let hash = 0;\n        for (let i = 0; i < str.length; i++) {\n            hash = (hash << 5) - hash + str.charCodeAt(i);\n            hash |= 0;\n        }\n        return Math.abs(hash);\n    }\n}\n\nconst lb = new CLILoadBalancer([\n    \"cli-1:4321\",\n    \"cli-2:4321\",\n    \"cli-3:4321\",\n]);\n\napp.post(\"/chat\", async (req, res) => {\n    const server = lb.getServerForUser(req.user.id);\n    const client = new CopilotClient({ cliUrl: server });\n\n    const session = await client.createSession({\n        sessionId: `user-${req.user.id}-chat`,\n        model: \"gpt-5.4\",\n    });\n\n    const response = await session.sendAndWait({ prompt: req.body.message });\n    res.json({ content: response?.data.content });\n});\n```\n\n### スティッキー セッションと共有ストレージ\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-5.png)\n\n**スティッキー セッション** の方が簡単で、ユーザーを特定の CLI サーバーにピン留めします。 共有ストレージは必要ありませんが、負荷分散は不均一です。\n\n**共有ストレージ** を使用すると、任意の CLI で任意のセッションを処理できます。 負荷分散が向上しますが、 `~/.copilot/session-state/`にはネットワークストレージが必要です。\n\n## 垂直スケーリング\n\n### 1 つの CLI サーバーのチューニング\n\n1 つの CLI サーバーで、多数の同時セッションを処理できます。 主な考慮事項:\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-6.png)\n\n**セッション ライフサイクル管理** は、垂直スケーリングの鍵となります。\n\n```typescript\n// Limit concurrent active sessions\nclass SessionManager {\n    private activeSessions = new Map<string, Session>();\n    private maxConcurrent: number;\n\n    constructor(maxConcurrent = 50) {\n        this.maxConcurrent = maxConcurrent;\n    }\n\n    async getSession(sessionId: string): Promise<Session> {\n        // Return existing active session\n        if (this.activeSessions.has(sessionId)) {\n            return this.activeSessions.get(sessionId)!;\n        }\n\n        // Enforce concurrency limit\n        if (this.activeSessions.size >= this.maxConcurrent) {\n            await this.evictOldestSession();\n        }\n\n        // Create or resume\n        const session = await client.createSession({\n            sessionId,\n            model: \"gpt-5.4\",\n        });\n\n        this.activeSessions.set(sessionId, session);\n        return session;\n    }\n\n    private async evictOldestSession(): Promise<void> {\n        const [oldestId] = this.activeSessions.keys();\n        const session = this.activeSessions.get(oldestId)!;\n        // Session state is persisted automatically — safe to disconnect\n        await session.disconnect();\n        this.activeSessions.delete(oldestId);\n    }\n}\n```\n\n## 一時的セッションと永続的セッション\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-7.png)\n\n### 一時的なセッション\n\n各要求が独立しているステートレス API エンドポイントの場合:\n\n```typescript\napp.post(\"/api/analyze\", async (req, res) => {\n    const session = await client.createSession({\n        model: \"gpt-5.4\",\n    });\n\n    try {\n        const response = await session.sendAndWait({\n            prompt: req.body.prompt,\n        });\n        res.json({ result: response?.data.content });\n    } finally {\n        await session.disconnect();  // Clean up immediately\n    }\n});\n```\n\n### 永続的セッション\n\n会話型インターフェイスまたは実行時間の長いワークフローの場合:\n\n```typescript\n// Create a resumable session\napp.post(\"/api/chat/start\", async (req, res) => {\n    const sessionId = `user-${req.user.id}-${Date.now()}`;\n\n    const session = await client.createSession({\n        sessionId,\n        model: \"gpt-5.4\",\n        infiniteSessions: {\n            enabled: true,\n            backgroundCompactionThreshold: 0.80,\n        },\n    });\n\n    res.json({ sessionId });\n});\n\n// Continue the conversation\napp.post(\"/api/chat/message\", async (req, res) => {\n    const session = await client.resumeSession(req.body.sessionId);\n    const response = await session.sendAndWait({ prompt: req.body.message });\n\n    res.json({ content: response?.data.content });\n});\n\n// Clean up when done\napp.post(\"/api/chat/end\", async (req, res) => {\n    await client.deleteSession(req.body.sessionId);\n    res.json({ success: true });\n});\n```\n\n## コンテナーのデプロイ\n\n### 永続ストレージ付きのKubernetes\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: copilot-cli\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: copilot-cli\n  template:\n    metadata:\n      labels:\n        app: copilot-cli\n    spec:\n      containers:\n        - name: copilot-cli\n          image: your-registry/copilot-cli:latest  # See backend-services.md for how to build and push this image\n          args: [\"--headless\", \"--host\", \"0.0.0.0\", \"--port\", \"4321\"]\n          env:\n            - name: COPILOT_GITHUB_TOKEN\n              valueFrom:\n                secretKeyRef:\n                  name: copilot-secrets\n                  key: github-token\n          ports:\n            - containerPort: 4321\n          volumeMounts:\n            - name: session-state\n              mountPath: /root/.copilot/session-state\n      volumes:\n        - name: session-state\n          persistentVolumeClaim:\n            claimName: copilot-sessions-pvc\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: copilot-cli\nspec:\n  selector:\n    app: copilot-cli\n  ports:\n    - port: 4321\n      targetPort: 4321\n```\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-8.png)\n\n### Azure Container Instances\n\n```yaml\ncontainers:\n  - name: copilot-cli\n    image: your-registry/copilot-cli:latest  # See backend-services.md for how to build and push this image\n    command: [\"copilot\", \"--headless\", \"--host\", \"0.0.0.0\", \"--port\", \"4321\"]\n    volumeMounts:\n      - name: session-storage\n        mountPath: /root/.copilot/session-state\n\nvolumes:\n  - name: session-storage\n    azureFile:\n      shareName: copilot-sessions\n      storageAccountName: myaccount\n```\n\n## 実稼働チェックリスト\n\n![図: 説明されたプロセスを示すフローチャート。](/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-9.png)\n\n| 懸念                                          | レコメンデーション                                         |\n| ------------------------------------------- | ------------------------------------------------- |\n| **セッションのクリーンアップ**                           | 定期的なクリーンアップを実行して TTL より古いセッションを削除する               |\n| **ヘルスチェック**                                 | CLI サーバーに定期的に ping を実行します。応答しない場合は再起動する           |\n| **Storage**                                 |                                                   |\n| `~/.copilot/session-state/` の永続ボリュームをマウントする |                                                   |\n| **シークレット**                                  | プラットフォームのシークレット マネージャー (Vault、K8s シークレットなど) を使用する |\n| **Monitoring**                              | アクティブなセッション数、応答の待機時間、エラー率を追跡する                    |\n| **Locking**                                 | 共有セッション アクセスに Redis または類似を使用する                    |\n| **シャットダウン**                                 | CLI サーバーを停止する前にアクティブなセッションをドレインする                 |\n\n## 制限事項\n\n| 制限事項                    | 詳細情報                                   |\n| ----------------------- | -------------------------------------- |\n| **組み込みのセッション ロックなし**    | 同時実行アクセス用にアプリケーション レベルのロックを実装する        |\n| **組み込みの負荷分散なし**         | 外部 LB またはサービスメッシュを使用する                 |\n| **セッションの状態はファイル ベースです** | マルチサーバーセットアップ用の共有ファイルシステムが必要           |\n| **30 分間の無操作タイムアウト**     | アクティビティのないセッションは CLI によって自動クリーンアップされます |\n| **CLI は単一プロセスです**       | スレッドではなく CLI サーバー インスタンスを追加してスケーリングする  |\n\n## 次のステップ\n\n* **[セッションの再開と永続化](/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/session-persistence)**: 再開可能なセッションの詳細\n* **[バックエンド サービスのセットアップ](/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/setup/backend-services)**: コア サーバー側のセットアップ\n* **[GitHub OAuth のセットアップ](/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/setup/github-oauth)**: マルチユーザー認証\n* **[BYOK (独自のキーを持ち込む)](/ja/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/auth/byok)**: 独自のモデル プロバイダーを使用する"}