# Escalonamento

> Escalone o Nodaro self-hosted para além de um contêiner: separe a API, os workers e o orquestrador e ajuste a concorrência, o Redis e o armazenamento.

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

**Escalonar** um Nodaro self-hosted significa rodar os processos dele em mais de um contêiner. A configuração padrão do Compose roda tudo em um único contêiner: a API, os workers, o orquestrador e o servidor web. Isso atende bem até cerca de 5 usuários ativos. Para mais usuários, rode os workers em contêineres próprios. Eles compartilham um Redis, um banco de dados e um bucket de armazenamento, e só se coordenam pelas filas do Redis.

## Os processos
O script de inicialização da imagem, `/app/start.sh`, inicia estes processos lado a lado, a partir de `/app/backend`:

| Comando | O que faz | Carga |
| --- | --- | --- |
| `node dist/server.js` | A API HTTP | Pouca CPU, memória moderada |
| `node dist/worker.js` | O worker de mídia: um job por nó, chama os provedores de modelos | Espera pela rede, executa muitos jobs ao mesmo tempo |
| `node dist/render-worker.js` | O renderizador: composições, em um Chrome headless | Limitado pela CPU: 1 ou 2 por máquina |
| `node dist/orchestrator.js` | O orquestrador de workflows: executa o grafo de cada workflow | Espera pela rede, pouca CPU |
| `node dist/pipeline-worker.js` | O pipeline **História → vídeo** (Story → Video), que só roda no Nodaro Cloud | Encerra na hora nas edições self-hosted |

O script de inicialização também faz quatro coisas que um contêiner que roda um único processo não faz:

- Ele roda o servidor web na porta `3000`, na frente da API.
- Ele aplica as migrações na stack incluída.
- Ele gera o `INTERNAL_ORCHESTRATOR_SECRET` quando a variável não está definida.
- Ele gera a chave de criptografia na stack incluída.

Leia o script na imagem antes de dividi-lo.

## Uma divisão típica
- **1 contêiner da API**, rodando `server.js`.
- **Vários contêineres de worker de mídia**. `VIDEO_WORKER_CONCURRENCY=50`, o padrão, serve para cada um.
- **1 ou 2 contêineres de worker de renderização**, cada um na própria máquina.
- **1 contêiner do orquestrador**.

Os contêineres nunca se comunicam diretamente. Todos usam o mesmo Redis, o mesmo Supabase e o mesmo armazenamento, e o Redis é o único ponto de coordenação.

## O que todos os contêineres precisam compartilhar
| Variável | Por que precisa ser igual |
| --- | --- |
| `INTERNAL_ORCHESTRATOR_SECRET` | O orquestrador se autentica na API com ela. O script de inicialização gera uma nova em cada contêiner quando ela não está definida, então defina-a explicitamente, com o mesmo valor em todos os lugares. |
| `NODARO_ENCRYPTION_KEY` | As chaves de provedor e as credenciais armazenadas são criptografadas com ela. Outra chave não consegue lê-las. |
| `RUNTIME_ENV` | Dá nome à instalação. Todos os contêineres de uma mesma instalação precisam usar o mesmo valor. |
| `EDITION`, `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `REDIS_URL` e as variáveis `R2_*` | A mesma edição, o mesmo banco de dados, as mesmas filas e o mesmo armazenamento em todos os contêineres. |

## Concorrência
| Variável | Padrão | O que ela limita |
| --- | --- | --- |
| `MAX_CONCURRENT_NODES_PER_EXECUTION` | `6`, no máximo `20` | Os nós que uma execução de workflow pode executar ao mesmo tempo. O teto de nós em paralelo para todo o servidor. |
| `VIDEO_WORKER_CONCURRENCY` | `50` | Jobs ao mesmo tempo em um worker de mídia |
| `ORCHESTRATOR_CONCURRENCY` | `20` | Jobs de workflow ao mesmo tempo em um orquestrador |
| `RENDER_WORKER_CONCURRENCY` | `2`, no máximo `10` | Renderizações ao mesmo tempo em um worker de renderização. Cada uma é um Chrome headless. |
| `REMOTION_CONCURRENCY` | `2` para cenas 3D; metade dos núcleos da CPU para as outras renderizações | Abas do navegador por renderização. Mantenha o valor baixo quando vários jobs 3D são executados ao mesmo tempo: cada aba WebGL acrescenta threads e conta para o limite de processos do contêiner. |
| `FFMPEG_CONCURRENCY` | `4`, no máximo `32` | Processos do ffmpeg ao mesmo tempo, somando todos os nós de edição de vídeo e de áudio |

O arquivo do Compose não repassa essas variáveis do `.env`. Adicione-as em `environment:` de cada serviço que precisa delas.

Cada nó de uma execução pode levar até 90 minutos, e uma execução inteira, até 120 minutos. Um nó que declara o próprio orçamento de tempo recebe esse orçamento, e o limite da execução cresce na mesma medida. O [**Aplicar EDL** (Apply EDL)](https://nodaro.ai/docs/nodes/video/apply-edl) é um nó assim: o orçamento dele depende da edição que ele renderiza.

## Alta disponibilidade do Redis
As filas de jobs aceitam o modo cluster do Redis. Defina `REDIS_URL` com um endpoint de cluster ou uma URL do Sentinel.

Além das filas, a API mantém pequenos caches compartilhados no Redis, para que vários contêineres da API não repitam, cada um, o mesmo trabalho lento junto aos provedores. Hoje, isso é o catálogo de avatares e vozes da HeyGen, de cerca de 4 MB. Um contêiner o atualiza sob um bloqueio (lock) a cada `HEYGEN_CATALOG_REFRESH_HOURS` horas, 24 por padrão, e os outros adotam a nova cópia em cerca de meio minuto. Tudo ali é cache: quando o Redis não pode ser acessado, cada contêiner usa a própria memória, e uma entrada perdida é preenchida de novo a partir do provedor na inicialização seguinte.

## Duas instalações, um banco de dados
Você pode apontar uma segunda instalação, como uma cópia de staging, para o **mesmo** projeto do Supabase, com um Redis **próprio**. Nesse caso, dê a cada instalação um `RUNTIME_ENV` diferente. No Railway, `RAILWAY_ENVIRONMENT_NAME` já faz isso.

Cada execução registra o nome da instalação cujo orquestrador a assumiu, e a limpeza de cada instalação só verifica as próprias execuções. Sem nomes distintos, cada instalação procura no próprio Redis os jobs da outra, não os encontra e marca execuções saudáveis como falhas com `Execution orphaned`. As execuções que começaram antes de as instalações registrarem os nomes não têm nome; a instalação chamada `production` cuida delas.

## Ciclo de vida do armazenamento
O Nodaro nunca apaga sozinho a mídia armazenada: ele só faz referência aos arquivos pela chave deles. Para expirar a mídia antiga, adicione uma regra de ciclo de vida ao seu bucket, por exemplo depois de 90 dias. Inclua o prefixo `video-analysis-tmp/` na regra: ele guarda arquivos temporários de análise, e as instalações self-hosted não têm um job de limpeza para eles.

## Parar contêineres no Railway
No Railway, `RAILWAY_DEPLOYMENT_DRAINING_SECONDS` define o tempo entre o sinal de parada e a parada forçada de um contêiner substituído. O worker de mídia drena durante esse tempo menos 5 segundos, para que uma chamada longa a um modelo consiga terminar e ser salva antes de o job dela passar para o novo contêiner. Sem a variável, o worker drena por 25 segundos.

## Frequently asked questions

### Quantos usuários um único contêiner do Nodaro consegue atender?

A configuração padrão, com um único contêiner, atende bem até cerca de 5 usuários ativos. Acima disso, rode os workers de mídia, os workers de renderização e o orquestrador em contêineres separados.

### Como os contêineres do Nodaro se comunicam?

Eles não se comunicam diretamente. Todos os contêineres se conectam ao mesmo Redis, ao mesmo banco de dados e ao mesmo armazenamento, e as filas de jobs no Redis são o único ponto de coordenação.

### Por que as minhas execuções aparecem como Execution orphaned?

Provavelmente duas instalações compartilham um banco de dados, mas usam instâncias separadas do Redis sem nomes distintos. Dê a cada instalação um valor próprio de RUNTIME_ENV e use o mesmo valor em todos os contêineres de uma mesma instalação.

### O Nodaro apaga a mídia antiga do meu bucket?

Não. O Nodaro faz referência à mídia armazenada e nunca a apaga sozinho. Adicione uma regra de ciclo de vida ao seu bucket para expirar os arquivos antigos e inclua nela o prefixo video-analysis-tmp/.
