# 다중 서버 배포

## Content

프로덕션 환경에서 **SpreadJS Collaboration Server**가 높은 동시성을 처리하거나 고가용성을 달성해야 하는 경우, 일반적으로 **로드 밸런서 뒤에 여러 서버 인스턴스**를 배포합니다.
그러나 사용자가 서로 다른 서버에 연결되어 있을 경우 **메시지 브로드캐스팅 문제**가 발생할 수 있습니다. 예를 들어, 동일한 동시 작업 룸에 있더라도 “서버 A”에 연결된 사용자는 “서버 B”에 연결된 사용자와 실시간 업데이트를 주고받을 수 없습니다.
이를 해결하기 위해 SpreadJS Collaboration은 **[Socket.IO Adapter](https://socket.io/docs/v4/adapter/)** 통합을 지원합니다. 이 어댑터는 모든 동시 작업 이벤트와 메시지를 서버 인스턴스 전반에 걸쳐 원활하게 분산시켜, 어떤 노드에 연결되어 있든 모든 클라이언트 간 실시간 동기화를 가능하게 합니다.

## 사전 요구 사항

| 구성 요소 | 설명 |
| ----- | --- |
| Node.js | v16 이상 |
| Redis Server | v7.0 이상, 모든 동시 작업 서버에서 접근 가능 |
| @mescius/js-collaboration | SpreadJS Collaboration 서버 패키지 |
| @socket.io/redis-adapter | 다중 노드 통신을 위한 Redis 어댑터 |
| Load Balancer | Nginx, Kubernetes Service 또는 동등한 구성 |

## 기능 설명

### 확장 가능한 동시 작업 아키텍처

* Socket.IO Redis Adapter를 사용한 **다중 서버 배포** 지원
* 서버 간 **실시간 동시 작업** **데이터**(워크북 업데이트, 선택 상태, 연결 이벤트) 동기화
* 모든 사용자 세션 전반에 걸친 **데이터 일관성 및 동기화 보장**
* **로드 밸런싱** 시스템(Nginx, Kubernetes 등)과 호환

### Redis Pub/Sub 메커니즘

어댑터는 Redis의 **Publish/Subscribe(Pub/Sub)** 를 사용하여 다음을 수행합니다.

* 한 서버에서 발생한 소켓 이벤트를 다른 서버로 브로드캐스트
* 동일한 동시 작업 룸의 업데이트를 모든 서버에 전달
* 서로 다른 인스턴스에 연결된 사용자 간 동기화 유지

## 사용 방법

### 의존성 설치

서버 프로젝트 디렉터리에서 다음 명령을 실행하고, Redis 서버가 실행 중이며 모든 노드에서 접근 가능한지 확인합니다.

```auto
npm install @mescius/js-collaboration
npm install redis
npm install @socket.io/redis-adapter
```

### Socket.IO Redis Adapter 적용

동시 작업 서버 설정 파일(예: `server.js`)에 다음 코드를 추가합니다.

```auto
import { Server } from '@mescius/js-collaboration';
import { createClient } from 'redis';
import { createAdapter } from '@socket.io/redis-adapter';

// 1. Pub/Sub 클라이언트 초기화
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);

// 2. 어댑터 주입
const server = new Server({
    port: 8080,
    socketIoAdapter: createAdapter(pubClient, subClient)
});
```

### 로드 밸런서 구성

여러 서버 인스턴스로 사용자 트래픽을 분산하기 위해 **로드 밸런서**가 필요합니다. 아래는 권장되는 두 가지 구성 예시입니다.

#### Nginx 배포 예시

Nginx를 리버스 프록시로 사용하여 **upstream** 블록에 서버 목록을 정의하고, **WebSocket** 프로토콜 업그레이드(Upgrade) 헤더를 설정할 수 있습니다.

* **Upstream**: 백엔드 서버 노드의 IP와 포트를 정의
* **Upgrade 헤더**: WebSocket 핸드셰이크를 지원하기 위해 `Upgrade`, `Connection` 헤더를 명시적으로 설정해야 함

```auto
http {
  # 백엔드 서버 목록 정의
  upstream nodes {
    server 103.32.2.101:3000;
    server 103.32.2.102:3000;
    server 103.32.2.103:3000;
  }

  server {
    listen 3000;
    server_name yourhost.com;

    location /collaboration {
      proxy_pass http://nodes;

      # WebSocket 지원 활성화
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      
      # 실제 IP 전달(선택 사항)
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;
    }
  }
}
```

***

#### Kubernetes(K8s) 배포 예시

Kubernetes는 **replicas**와 **서비스 노출**을 통해 확장 가능하고 자체 복구가 가능한 배포 방식을 제공합니다.

* **replicas**: 다중 서버 배포를 위해 3 이상으로 설정
* **환경 변수**: 모든 Pod가 동일한 Redis 서비스에 연결되도록 `env` 변수를 통해 Redis 및 데이터베이스 주소 전달

```auto
apiVersion: apps/v1
kind: Deployment
metadata:
  name: collaboration-app
spec:
  replicas: 3 # 다중 서버 구성을 위한 레플리카 수
  selector:
    matchLabels:
      app: collaboration-app
  template:
    metadata:
      labels:
        app: collaboration-app
    spec:
      containers:
        - name: collaboration-app
          image: collaboration-app:v1 # 실제 이미지로 교체
          ports:
            - containerPort: 3000
          # 공유 리소스 연결을 보장하기 위한 환경 변수 설정
          env:
            - name: DB_HOST
              value: "postgres-service" # K8s DB 서비스 이름
            - name: REDIS_HOST
              value: "redis-service"    # K8s Redis 서비스 이름
---
apiVersion: v1
kind: Service
metadata:
  name: collaboration-app-service
spec:
  selector:
    app: collaboration-app
  ports:
    - port: 3000
      targetPort: 3000
  type: LoadBalancer
```