{"meta":{"title":"확장성 및 멀티 테넌시","intro":"여러 사용자에게 서비스를 제공하고, 동시 세션을 처리하고, 인프라 전체에서 수평으로 확장하도록 Copilot SDK 배포를 디자인합니다. 이 가이드에서는 세션 격리 패턴, 크기 조정 토폴로지 및 프로덕션 모범 사례를 설명합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/copilot","title":"GitHub Copilot"},{"href":"/ko/copilot/how-tos","title":"방법"},{"href":"/ko/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/copilot/how-tos/copilot-sdk/setup","title":"Copilot SDK 설정"},{"href":"/ko/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 수준 옵션 및 패턴은 [다중 테넌트 및 서버 배포](/ko/copilot/how-tos/copilot-sdk/setup/multi-tenancy)을 참조하세요.\n\n**최적 대상:** 플랫폼 개발자, SaaS 작성기, 소수의 동시 사용자를 제공하는 모든 배포.\n\n## 핵심 개념\n\n패턴을 선택하기 전에 세 가지 크기 조정 차원을 이해합니다.\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여러 사용자가 하나의 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여러 사용자가 코필로트와 공유 채팅방과 같은 동일한 세션과 상호 작용합니다.\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)  | 낮음(하나의 명령줄 인터페이스) | 낮음(CLI 1회 + 세션 1회) |\n| **복잡성**       | 중간            | 낮음                | 높음(잠금)             |\n| **인증 유연성**    |               |                   |                    |\n| ✅ 사용자별 토큰     |               |                   |                    |\n| ⚠️ 서비스 토큰     |               |                   |                    |\n| ⚠️ 서비스 토큰     |               |                   |                    |\n| **최적입니다**     | 다중 사용자 SaaS   | 내부 도구             | Collaboration      |\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### 단일 CLI 서버 튜닝\n\n단일 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 컨테이너 인스턴스\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| 관심사                                   | Recommendation                               |\n| ------------------------------------- | -------------------------------------------- |\n| **세션 정리**                             | 주기적 정리를 실행하여 TTL보다 오래된 세션을 삭제합니다.            |\n| **상태 검사**                             | 주기적으로 CLI 서버 Ping; 응답하지 않는 경우 다시 시작          |\n| **스토리지**                              |                                              |\n| `~/.copilot/session-state/`에 영구 볼륨 탑재 |                                              |\n| **비밀**                                | 플랫폼의 시크릿 관리 도구(Vault, K8s Secrets 등)를 사용하세요. |\n| **Monitoring**                        | 활성 세션 수, 응답 대기 시간, 오류 비율 추적                  |\n| **Locking**                           | 공유 세션 액세스에 Redis 또는 이와 유사한 항목 사용             |\n| **종료**                                | CLI 서버를 중지하기 전에 활성 세션 드레이닝                   |\n\n## Limitations\n\n| Limitation           | Details                           |\n| -------------------- | --------------------------------- |\n| **기본 제공 세션 잠금 없음**   | 동시 액세스를 위한 애플리케이션 수준 잠금 구현        |\n| **기본 제공 부하 분산 없음**   | 외부 LB 또는 서비스 메시를 사용하세요            |\n| **세션 상태는 파일 기반입니다.** | 다중 서버 설치를 위한 공유 파일 시스템 필요         |\n| **30분 유휴 시간 제한**     | 작업이 없는 세션은 CLI에 의해 자동으로 정리됩니다.    |\n| **CLI는 단일 프로세스입니다.** | 스레드가 아닌 CLI 서버 인스턴스를 더 추가하여 크기 조정 |\n\n## 다음 단계\n\n* **[세션 다시 시작 및 지속성](/ko/copilot/how-tos/copilot-sdk/features/session-persistence)**: 다시 실행 가능한 세션에 대한 심층 분석\n* **[백 엔드 서비스 설정](/ko/copilot/how-tos/copilot-sdk/setup/backend-services)**: 핵심 서버 쪽 설정\n* **[GitHub OAuth 설정](/ko/copilot/how-tos/copilot-sdk/setup/github-oauth)**: 다중 사용자 인증\n* **[BYOK(사용자 고유의 키 가져오기)](/ko/copilot/how-tos/copilot-sdk/auth/byok)**: 사용자 고유의 모델 공급자 사용"}