O que escala e como
| Componente | Escala | Observações |
|---|---|---|
| app | Horizontal | Sem estado. Requer Redis além de uma réplica |
| realtime | Horizontal | Requer Redis além de uma réplica (adaptador Socket.IO) |
| postgresql | Vertical + réplicas de leitura | O gargalo inevitável |
| redis | Vertical / par em HA | Apenas coordenação; pequeno |
| cronjobs | Fixo | Uma chamada por tick, independente do número de réplicas |
Pré-requisitos antes de passar de uma réplica
O Redis precisa estar acessível antes de você aumentar o replicaCount. Os dois modelos de deployment já o incluem por padrão, então isso já está atendido — a menos que você tenha definido redis.enabled: false (Helm) ou removido o serviço redis (Compose) sem fornecer REDIS_URL. Sem Redis, o pub/sub cai para um emissor local ao processo e o adaptador Socket.IO não tem transporte entre pods — o realtime registra uma linha na inicialização indicando modo de pod único e depois descarta os eventos entre pods silenciosamente. Veja Redis.
Você também precisa de armazenamento de objetos compartilhado — o armazenamento em disco local é por pod, então um arquivo enviado por uma réplica fica invisível para as outras. Veja Armazenamento de Objetos.
Escalando o app
app:
replicaCount: 3
resources:
limits:
memory: 8Gi
cpu: 2000m
requests:
memory: 4Gi
cpu: 1000mA restrição é memória, não CPU. As execuções de workflow rodam dentro do processo do app em sandboxes isolated-vm, e o parsing de arquivos acontece em memória. A telemetria de produção mostra 4–8 GB constantes, com picos de até 12 GB sob carga alta de execução. Se você provisionar memória de menos, terá OOMKills que encerram execuções de workflow em andamento.
Um PodDisruptionBudget é criado automaticamente quando replicaCount > 1 (maxUnavailable: 25%). Aperte-o com podDisruptionBudget.minAvailable se precisar.
Autoscaling
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80Requer o metrics-server. Quando ativado, o chart omite spec.replicas para que o HPA assuma o controle do número de réplicas.
A redução de escala encerra pods que podem estar executando workflows. Defina um minReplicas conservador e considere um bloco behavior com um stabilizationWindowSeconds longo na redução, para que execuções longas não sejam interrompidas repetidamente.
O realtime recebe o mesmo HPA, a menos que você o desative — e, de novo, só escale além de uma réplica com o Redis configurado:
autoscaling:
realtime:
enabled: falseBanco de dados
O Postgres é onde escalar deixa de ser uma questão de réplicas.
Conexões
Cada réplica do app abre um pool. O total de conexões cresce com o número de réplicas, e o Postgres tem um max_connections rígido. Um deployment que funciona com 2 réplicas pode esgotar as conexões com 6.
Faça a conta: réplicas × tamanho do pool + realtime + cronjobs + migrations + folga precisa ficar abaixo de max_connections.
Para qualquer coisa acima de algumas poucas réplicas, coloque o PgBouncer em modo de transaction pooling na frente do banco e aponte DATABASE_URL para ele. Essa é a mudança de maior impacto em um deployment grande — ela desacopla o número de réplicas do app do número de conexões com o banco.
Réplicas de leitura
Caminhos de leitura pesados — listagem de logs, logs de auditoria, agregações de dashboard — podem ser transferidos:
DATABASE_REPLICA_URL=postgresql://user:pass@replica-host:5432/studioAs leituras voltam para o primário quando a variável não está definida. Existem overrides por papel, caso componentes diferentes devam usar réplicas diferentes:
| Variável | Aplica-se a |
|---|---|
DATABASE_REPLICA_URL | Padrão para todos os papéis |
DATABASE_REPLICA_URL_WEB | O app web |
DATABASE_REPLICA_URL_REALTIME | O serviço de realtime |
DATABASE_REPLICA_URL_TRIGGER | Workers do Trigger.dev |
Réplicas têm atraso. O Studio roteia para elas apenas leituras tolerantes a latência, mas se a sua réplica ficar muito atrasada, logs recém-gravados podem não aparecer por um instante. Monitore o atraso de replicação.
Dimensionamento
| Deployment | Instância | Armazenamento |
|---|---|---|
| Pequeno (1–5 pessoas) | 2 vCPU / 8 GB | 50 GB |
| Padrão (5–50 pessoas) | 4 vCPU / 16 GB | 100 GB+ |
| Grande (50+ pessoas) | 8+ vCPU / 32 GB+ | 250 GB+, com crescimento automático |
Os embeddings da Knowledge Base são o principal motor de crescimento — o armazenamento vetorial escala com o volume de documentos, não com o número de pessoas. Ative o aumento automático de armazenamento.
Concorrência de execução
SCHEDULE_EXECUTION_CONCURRENCY_LIMIT (padrão 30) limita as execuções agendadas por instância do app. As outras três variáveis *_EXECUTION_CONCURRENCY_LIMIT valem apenas para o Trigger.dev e são inertes em uma auto-hospedagem padrão — veja Jobs em Background.
Quando as execuções ficam na fila mas a memória está tranquila, aumente o limite; quando a memória é o teto, adicione réplicas em vez disso.
Rate limits e cotas
Deployments auto-hospedados rodam sem limites de plano por padrão — sem rate limits, sem timeouts de execução, sem tetos de Tables e armazenamento. Cada um pode ser reativado individualmente; a lista de variáveis e os valores sugeridos estão em Variáveis de Ambiente.
Vale definir um timeout de execução mesmo em um deployment sem outros limites — é ele que impede um workflow descontrolado de segurar um sandbox indefinidamente.
Topologia de referência
Um deployment de produção atendendo cerca de 100 pessoas ativas:
app:
replicaCount: 3
resources:
limits: { memory: 8Gi, cpu: 2000m }
requests: { memory: 4Gi, cpu: 1000m }
env:
REDIS_URL: "rediss://:<password>@redis.internal:6380"
realtime:
replicaCount: 2
env:
REDIS_URL: "rediss://:<password>@redis.internal:6380"
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "pgbouncer.internal"
port: 6432
database: studio
sslMode: require
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
podDisruptionBudget:
minAvailable: 2Além disso: Postgres gerenciado com PITR, Redis gerenciado em um tier de HA, armazenamento de objetos com versionamento e imagens fixadas em uma tag explícita.