# 확장

> 셀프 호스팅 Nodaro를 컨테이너 하나 이상으로 확장하세요. API, 미디어 워커, 렌더링 워커, 오케스트레이터를 분리하고 동시 실행, Redis, 스토리지를 조정합니다.

Source: https://nodaro.ai/ko/docs/self-hosting/scaling

셀프 호스팅 Nodaro를 **확장**한다는 것은 그 프로세스를 여러 컨테이너에서 실행한다는 뜻입니다. 기본 Compose 구성은 API, 워커, 오케스트레이터, 웹 서버를 모두 컨테이너 하나에서 실행합니다. 활성 사용자 약 5명까지는 이것으로 충분합니다. 그보다 많으면 워커를 별도 컨테이너에서 실행하세요. 워커들은 Redis 하나, 데이터베이스 하나, 스토리지 버킷 하나를 공유하며, Redis의 큐로만 조정합니다.

## 프로세스
이미지의 시작 스크립트 `/app/start.sh`는 `/app/backend`에서 다음 프로세스를 나란히 시작합니다.

| 명령 | 역할 | 부하 |
| --- | --- | --- |
| `node dist/server.js` | HTTP API | 낮은 CPU, 보통 수준의 메모리 |
| `node dist/worker.js` | 미디어 워커: 노드마다 작업 하나를 맡아 모델 공급자를 호출합니다. | 네트워크를 기다리며, 많은 작업을 동시에 실행합니다. |
| `node dist/render-worker.js` | 렌더러: 헤드리스 Chrome에서 컴포지션을 렌더링합니다. | CPU에 따라 제한됩니다. 머신당 1~2개. |
| `node dist/orchestrator.js` | 워크플로 오케스트레이터: 각 워크플로의 그래프를 실행합니다. | 네트워크를 기다리며, CPU 사용량이 낮습니다. |
| `node dist/pipeline-worker.js` | Nodaro Cloud에서만 실행되는 스토리 → 동영상 파이프라인 | 셀프 호스팅 에디션에서는 바로 종료됩니다. |

시작 스크립트는 단일 프로세스를 실행하는 컨테이너가 하지 않는 일 네 가지도 합니다.

- API 앞에서 포트 `3000`의 웹 서버를 실행합니다.
- 번들된 스택에서 마이그레이션을 적용합니다.
- `INTERNAL_ORCHESTRATOR_SECRET`이 설정되지 않았으면 생성합니다.
- 번들된 스택에서 암호화 키를 생성합니다.

분리하기 전에 이미지 안의 스크립트를 읽어 보세요.

## 일반적인 분리 구성
- **API 컨테이너 1개**: `server.js`를 실행합니다.
- **미디어 워커 컨테이너 여러 개**: 각각 기본값인 `VIDEO_WORKER_CONCURRENCY=50`으로 충분합니다.
- **렌더링 워커 컨테이너 1~2개**: 각각 별도 머신에서 실행합니다.
- **오케스트레이터 컨테이너 1개**.

컨테이너끼리는 절대 직접 통신하지 않습니다. 모두 같은 Redis, Supabase, 스토리지를 쓰며, Redis가 유일한 조정 지점입니다.

## 모든 컨테이너가 공유해야 하는 것
| 변수 | 같아야 하는 이유 |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | 오케스트레이터가 이 값으로 API에 인증합니다. 설정되지 않았으면 시작 스크립트가 컨테이너마다 새 값을 생성하므로, 모든 곳에 같은 값을 명시적으로 설정하세요. |
| `NODARO_ENCRYPTION_KEY` | 저장된 공급자 키와 자격 증명이 이 키로 암호화됩니다. 다른 키로는 읽을 수 없습니다. |
| `RUNTIME_ENV` | 설치의 이름입니다. 한 설치의 모든 컨테이너가 같은 값을 써야 합니다. |
| `EDITION`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `REDIS_URL`, `R2_*` 변수 | 모든 컨테이너에서 에디션, 데이터베이스, 큐, 스토리지가 같아야 합니다. |

## 동시 실행
| 변수 | 기본값 | 제한하는 것 |
| --- | --- | --- |
| `MAX_CONCURRENT_NODES_PER_EXECUTION` | `6`, 최대 `20` | 워크플로 실행 하나가 동시에 실행할 수 있는 노드 수입니다. 서버 전체의 병렬 노드 상한입니다. |
| `VIDEO_WORKER_CONCURRENCY` | `50` | 미디어 워커 하나가 동시에 처리하는 작업 수 |
| `ORCHESTRATOR_CONCURRENCY` | `20` | 오케스트레이터 하나가 동시에 처리하는 워크플로 작업 수 |
| `RENDER_WORKER_CONCURRENCY` | `2`, 최대 `10` | 렌더링 워커 하나가 동시에 처리하는 렌더링 수입니다. 각 렌더링이 헤드리스 Chrome 하나입니다. |
| `REMOTION_CONCURRENCY` | 3D 장면은 `2`, 다른 렌더링은 CPU 코어 수의 절반 | 렌더링 하나당 브라우저 탭 수입니다. 3D 작업 여러 개가 실행될 때는 낮게 유지하세요. WebGL 탭마다 스레드가 늘어나고 컨테이너의 프로세스 한도에 포함됩니다. |
| `FFMPEG_CONCURRENCY` | `4`, 최대 `32` | 모든 동영상 및 오디오 편집 노드에서 동시에 실행되는 ffmpeg 프로세스 수 |

Compose 파일은 `.env`에서 이 변수들을 전달하지 않습니다. 필요한 각 서비스의 `environment:` 아래에 추가하세요.

실행의 각 노드는 최대 90분, 실행 전체는 최대 120분까지 걸릴 수 있습니다. 자체 시간 예산을 선언한 노드는 대신 그 예산을 받고, 실행의 한도도 같은 만큼 늘어납니다. [EDL 적용](https://nodaro.ai/docs/nodes/video/apply-edl)이 그런 노드입니다. 예산은 렌더링하는 편집 내용에 따라 달라집니다.

## Redis 고가용성
작업 큐는 Redis 클러스터 모드를 지원합니다. `REDIS_URL`을 클러스터 엔드포인트나 Sentinel URL로 설정하세요.

API는 큐 외에도 Redis에 작은 공유 캐시를 둡니다. 그래서 여러 API 컨테이너가 같은 느린 공급자 작업을 각자 반복하지 않습니다. 현재는 약 4MB 크기의 HeyGen 아바타 및 보이스 카탈로그가 여기에 해당합니다. 컨테이너 하나가 잠금을 걸고 `HEYGEN_CATALOG_REFRESH_HOURS`마다(기본값 24) 이를 새로 고치며, 다른 컨테이너는 30초쯤 안에 새 사본을 받아 씁니다. 여기에 있는 것은 모두 캐시입니다. Redis에 연결할 수 없으면 각 컨테이너가 자체 메모리를 쓰고, 잃어버린 항목은 다음 부팅 때 공급자에서 다시 채워집니다.

## 설치 두 개, 데이터베이스 하나
스테이징 사본 같은 두 번째 설치를 **같은** Supabase 프로젝트에 연결하고 **별도의** Redis를 쓰게 할 수 있습니다. 이때 설치마다 다른 `RUNTIME_ENV`를 지정하세요. Railway에서는 `RAILWAY_ENVIRONMENT_NAME`이 이미 이 역할을 합니다.

각 실행에는 그 실행을 가져간 오케스트레이터가 속한 설치의 이름이 기록되며, 각 설치의 정리 작업은 자기 실행만 확인합니다. 이름이 구분되지 않으면 각 설치가 자기 Redis에서 다른 설치의 작업을 찾다가 찾지 못하고, 정상인 실행을 `Execution orphaned`로 실패 처리합니다. 설치가 이름을 기록하기 전에 시작된 실행에는 이름이 없으며, 이름이 `production`인 설치가 이를 처리합니다.

## 스토리지 수명 주기
Nodaro는 저장된 미디어를 스스로 삭제하지 않습니다. 키로 파일을 참조할 뿐입니다. 오래된 미디어가 만료되게 하려면 버킷에 수명 주기 규칙을 추가하세요. 예를 들어 90일 후입니다. 규칙에 `video-analysis-tmp/` 접두사를 포함하세요. 이 접두사에는 임시 분석 파일이 있으며, 셀프 호스팅 설치에는 이를 정리하는 작업이 없습니다.

## Railway에서 컨테이너 중지
Railway에서는 `RAILWAY_DEPLOYMENT_DRAINING_SECONDS`가 교체되는 컨테이너의 중지 신호와 강제 중지 사이의 시간을 정합니다. 미디어 워커는 그 시간에서 5초를 뺀 만큼 남은 작업을 마무리하므로, 긴 모델 호출도 작업이 새 컨테이너로 옮겨지기 전에 끝나고 저장될 수 있습니다. 설정하지 않으면 워커는 25초 동안 마무리합니다.

## 자주 묻는 질문

### Nodaro 컨테이너 하나로 사용자 몇 명까지 지원할 수 있나요?

기본 단일 컨테이너 구성은 활성 사용자 약 5명까지 충분합니다. 그보다 많으면 미디어 워커, 렌더링 워커, 오케스트레이터를 별도 컨테이너에서 실행하세요.

### Nodaro 컨테이너끼리는 어떻게 통신하나요?

직접 통신하지 않습니다. 모든 컨테이너가 같은 Redis, 데이터베이스, 스토리지에 연결하며, Redis의 작업 큐가 유일한 조정 지점입니다.

### 실행이 “Execution orphaned”로 표시되는 이유는 무엇인가요?

두 설치가 데이터베이스 하나를 공유하면서 서로 다른 Redis를 쓰고, 각자의 이름이 구분되지 않았을 가능성이 높습니다. 설치마다 고유한 RUNTIME_ENV 값을 지정하고, 한 설치의 모든 컨테이너에는 같은 값을 쓰세요.

### Nodaro가 버킷의 오래된 미디어를 삭제하나요?

아니요. Nodaro는 저장된 미디어를 참조할 뿐 스스로 삭제하지 않습니다. 오래된 파일이 만료되도록 버킷에 수명 주기 규칙을 추가하고, 그 규칙에 video-analysis-tmp/ 접두사를 포함하세요.
