MÓDULO 07 · CONCEITO 01 DE 12

Vertical vs horizontal

Scale up vs scale out. Custos relativos, limites físicos do hardware único, por que horizontal venceu em web e por que vertical ainda ganha em banco. A escolha que define a topologia toda do sistema antes mesmo da primeira linha.

Tempo de leitura ~22 min Pré-requisito Módulo 06 (latência e throughput) Próximo Stateless é o que escala

Em fevereiro de 2003, no relatório técnico "The Datacenter as a Computer" que se tornou livro em 2009, Luiz André Barroso e Urs Hölzle do Google articularam uma tese que mudou a engenharia de sistemas: máquinas individuais grandes — mainframes, servidores topo de linha — sempre vão perder para frotas de máquinas modestas no longo prazo, por uma simples questão de economia. O custo por unidade de capacidade cresce não-linearmente com o tamanho da máquina; o custo de uma frota cresce quase linearmente. Em escala, frota vence. Foi essa observação econômica, mais que técnica, que sustentou a aposta do Google em commodity hardware desde os anos 90 — e o que viralizou para o resto da indústria com a popularização da cloud nos anos 2010.

A escolha entre scale up (deixar uma máquina maior) e scale out (somar muitas máquinas) parece técnica e é, antes de tudo, arquitetural. Sistemas projetados para scale up tendem a ser monolitos com estado in-memory e thread pools grandes; sistemas para scale out tendem a ser stateless, distribuídos, e particionados desde o início. As duas escolhas levam a códigos qualitativamente diferentes — você não migra trivialmente de uma para a outra. Por isso a decisão merece consciência desde o design.

A história canônica diz que "horizontal venceu", mas é mais sutil. Para serviços de aplicação web, sim: stateless atrás de load balancer é o padrão universal em 2026. Para bancos de dados, depende: Postgres, MySQL, SQL Server frequentemente operam verticalmente até centenas de gigabytes; só então sharding entra. Para caches, varia: Redis pode ser single-instance ou cluster. Para data warehouses (Snowflake, BigQuery), horizontal sempre. Conhecer o padrão de cada classe importa.

Este conceito articula os trade-offs em precisão. Custos econômicos (hardware vs operação), custos de complexidade (single instance vs cluster distribuído), limites físicos (uma máquina nunca vai ser maior que a maior CPU disponível), e a regra prática "scale up até começar a doer, depois scale out". Os onze conceitos seguintes do módulo destrincham as ferramentas e padrões para fazer scale out funcionar; aqui o foco é entender a escolha antecipadamente.

Definições — o que é cada coisa

Scale up (vertical): aumentar a capacidade da máquina existente. Mais CPU, mais RAM, disco mais rápido. Em cloud, mudar de m5.large para m5.16xlarge; no banco, de instância de 8 cores para 96 cores. A operação é "paro o serviço, mudo a máquina, subo de novo" — single point de mudança, não exige coordenação distribuída.

Scale out (horizontal): adicionar mais máquinas de mesma capacidade ou similar. Distribuir o trabalho entre elas via load balancer (para serviços), sharding (para banco), ou consistent hashing (para cache distribuído). A operação é "subo mais uma instância, ela entra na frota automaticamente" — exige que o sistema saiba distribuir e que cada instância seja substituível.

Há também combinações: scale out de instâncias maiores (frota de máquinas grandes), e scale up de cluster (cada nó do cluster fica maior). Em sistemas reais, ambos acontecem ao longo do tempo. O importante é reconhecer qual eixo está sendo movido em cada decisão.

Custos — onde scale up empata e onde perde

Hardware moderno tem comportamento não-linear de preço. Considere preços de instâncias EC2 da AWS em 2026 (números aproximados, ordem de magnitude):

m6i.large    (2  vCPU,  8 GB RAM)  ~$0.10/h     $0.05 por vCPU/h
m6i.xlarge   (4  vCPU, 16 GB RAM)  ~$0.20/h     $0.05 por vCPU/h
m6i.4xlarge  (16 vCPU, 64 GB RAM)  ~$0.80/h     $0.05 por vCPU/h
m6i.16xlarge (64 vCPU, 256 GB)     ~$3.20/h     $0.05 por vCPU/h
m6i.32xlarge (128 vCPU, 512 GB)    ~$6.50/h     $0.051 por vCPU/h
x2iezn.12xl  (48 vCPU, 1.5 TB RAM) ~$10.00/h    $0.21 por vCPU/h ← memory-optimized
u-24tb1.metal (448 vCPU, 24 TB)    ~$110.00/h   $0.245 por vCPU/h ← bare metal SAP HANA

Para instâncias da família m6i (general purpose moderna), o preço por vCPU é praticamente constante em uma faixa enorme. Scale up dentro dessa faixa não é mais caro que scale out — 32× m6i.large custa o mesmo que 1× m6i.16xlarge. Então por que escolher uma sobre a outra?

A diferença aparece em três lugares.

1. Para hardware especializado (memory-optimized, bare metal, GPU). Uma única máquina com 24 TB de RAM custa muito mais por unidade de capacidade que uma frota de máquinas modestas. Esse é o ponto de Barroso/Hölzle. Sistemas que precisam de máquinas gigantes pagam prêmio significativo.

2. Para tolerância a falha. Uma única máquina, por mais cara, é single point of failure. Frota de 32 máquinas pode perder uma (3% de capacidade) sem cair. Para ter o mesmo efeito com scale up, você precisa de duas máquinas grandes (active-passive ou active-active) — dobrando o custo do hardware especializado. Frota tem tolerância a falha embutida no design; máquina única paga ela como custo extra.

3. Para variabilidade de carga. Scale out permite ajustar capacidade granularmente — adicionar 5 instâncias durante pico de tráfego, remover depois. Scale up é binário no curto prazo — você não troca a máquina por uma menor por uma hora à noite. Para sistemas com tráfego variável, scale out é dramaticamente mais econômico.

Para hardware commodity em workload constante e bem tolerante (ou compensável) a SPOF, vertical e horizontal empatam em custo de hardware. Daí o empate ser quebrado por outros fatores — operação, complexidade, limites físicos.

Limites físicos do scale up

Há um teto. Em 2026, a maior CPU x86 tem ~128 cores em socket único (AMD EPYC 9754, ARM Graviton4 com até 96 cores). Servidores topo de linha aproveitam 2 ou 4 sockets, chegando a 384–512 cores. RAM máxima em servidor commodity moderno é 4–6 TB; em especializado (HPE Superdome, IBM Power), até 64 TB. Esse é o limite — e ele cresce devagar (lei de Moore desacelerou). Não há "scale up infinito"; em algum ponto, a máquina maior simplesmente não existe.

Para sistemas com tráfego que pode cruzar esse teto, scale out não é escolha; é necessidade. Google, Facebook, AWS, Azure — todos operam infraestrutura cuja capacidade total não cabe em máquina nenhuma. Mesmo bancos: Aurora MySQL/PostgreSQL com Aurora Serverless escala horizontalmente em compute layer, apesar do storage ser pseudoshared. CockroachDB, Spanner, TiDB, Cassandra, ScyllaDB, DynamoDB — todos são distribuídos por construção.

Para sistemas que ainda cabem em uma máquina (a maioria das aplicações empresariais), scale up continua viável. Postgres em db.r6g.16xlarge (96 cores, 768 GB) sustenta carga de muitos sistemas sem sharding.

Complexidade — o custo invisível de scale out

Scale out tem custo qualitativo que não aparece na fatura cloud — aparece em horas de engenheiro. Operar cluster distribuído é fundamentalmente mais difícil que operar máquina única. Os problemas adicionais:

Coordenação. Como instâncias se descobrem? Como elas decidem quem é primário? Como elas balanceiam carga? Soluções existem (Consul, etcd, Kubernetes services), mas adicionam camadas.

Estado distribuído. Se cada instância tem estado, replicar entre elas é problema novo. Se externalizam estado, agora há rede entre instância e store de estado — latência adicional.

Particionamento. Em banco distribuído, escolher como particionar dados é decisão difícil que afeta queries futuras (covered em conceito 06 do módulo).

Falhas parciais. Em sistema distribuído, parte do sistema pode estar fora enquanto outra parte funciona. Cliente vê comportamento inconsistente. Diagnosticar fica mais complicado.

Network partitioning. CAP theorem (conceito 07 do módulo) — sistemas distribuídos precisam decidir, em particionamento de rede, entre consistência e disponibilidade.

Operação. Deploy de cluster de 50 máquinas é ordem de magnitude mais complexo que deploy em 1 máquina. Atualizar uma versão exige rolling update, drain de conexões, validação de saúde. Ferramentas modernas (Kubernetes, ECS) ajudam, mas a complexidade está lá.

Para times pequenos com tráfego modesto, esse custo operacional pode superar o benefício. Stack monolito em VM grande com replica passiva pode entregar 99.9% de SLA com fração da complexidade de Kubernetes cluster. A regra: se o time não tem experiência operacional para sustentar cluster, scale up até começar a doer.

Quando vertical ainda ganha

Apesar do "horizontal venceu", há classes de sistema onde vertical é preferível ou inevitável.

Banco transacional pequeno-médio. ACID exige coordenação que cluster distribuído paga caro (consenso, two-phase commit). Para bancos com até alguns terabytes, single instance + read replicas + backup é mais simples e mais rápido que Cassandra/CockroachDB.

Cache compartilhado. Redis single-instance é altíssima performance (sub-ms, > 100k ops/s em hardware modesto). Redis Cluster adiciona complexidade (resharding, slot migration); vale só quando o dataset não cabe em uma única instância (acima de ~100 GB).

Workload analítico em DB single-node. DuckDB, ClickHouse single, Postgres com extensions analíticas — todos podem processar bilhões de linhas em uma máquina topo de linha. Para datasets de até alguns TB, single-node é mais barato que data warehouse distribuído.

Sistemas legados. Aplicações com estado em memória, sessões locais, locks shared-everything — refatorar para horizontal pode custar mais que aumentar a máquina e aceitar o teto. Decisão pragmática.

Workloads com afinidade entre dados e computação. Em ML inference de modelos grandes, latência depende de manter o modelo na GPU. Scale out exige replicar modelo em N GPUs (caro). Scale up em GPU maior pode ser mais econômico.

A regra prática — scale up até doer

A heurística que sêniores adotam, depois de muitos sistemas: scale up até começar a doer, depois scale out. Significa: comece com a máquina modestamente grande (não a menor possível, não a maior). Quando ela começar a saturar, primeiro considere se subir o tamanho resolve por mais um ciclo. Se sim, faz isso — é caminho mais barato em complexidade. Quando subir mais não resolve (limite físico, custo non-linear, SPOF inaceitável), aí migra para scale out.

O timing importa. Migrar para scale out cedo demais paga o custo de complexidade quando ainda não há benefício. Migrar tarde demais força refatoração sob pressão (sistema em produção sob carga, reescrever em modo emergencial). O sinal de "está na hora" vem de combinações: máquina com 70%+ de CPU sustentada, latência subindo, custo da próxima máquina maior crescendo non-linearmente, ou necessidade de tolerância a falha que vertical não entrega.

Para serviços stateless de aplicação, excepcionalmente, vale começar horizontal desde o início. Não porque já é necessário — porque o custo de horizontal é trivial em sistema stateless (basta load balancer + 2 instâncias atrás), e essa estrutura paga em disponibilidade desde o dia 1. Bancos e caches, ao contrário, ganham começando vertical.

Os três cenários típicos

Para concretizar, considere três sistemas típicos e a escolha que cada um sustenta.

Cenário 1 — API web stateless de tráfego médio

Aplicação web ou API com 100 RPS médios, 1000 RPS em pico. Stateless por construção (sessão em Redis, dados em Postgres). Escolha: 2-3 instâncias atrás de load balancer desde o início, auto-scaling reativo configurado para picos. Banco e cache em vertical com replicas para read.

Justificativa: scale out aqui é trivial e entrega tolerância a falha (uma instância pode cair). O custo de complexidade é mínimo — todo cloud provider tem load balancer pronto. O custo extra de hardware é pequeno em comparação ao ganho de SLA.

Cenário 2 — Banco transacional médio

Sistema empresarial com 1 TB de dados, 1000 TPS, necessidade de ACID. Escolha: Postgres single primary em db.r6g.4xlarge (16 cores, 128 GB), com 2 read replicas para leitura. Backup contínuo (PITR). Sem sharding, sem cluster distribuído.

Justificativa: 1 TB cabe folgado em RAM moderna; 1000 TPS é facilmente sustentado por hardware atual; ACID em distributed costaria muita complexidade. Escalar vertical até atingir limite (10 TB, dezenas de milhares de TPS) — só então pensar sharding.

Cenário 3 — Plataforma global de mensageria

Sistema com 100 mil RPS sustentados, latência cross-region requerida, dataset de petabytes. Escolha: horizontal por construção, em todos os níveis. Aplicação stateless, Cassandra/Scylla ou DynamoDB para storage, Kafka particionado por tenant_id, deploy em múltiplas regiões.

Justificativa: nenhum dos limites verticais sustentaria essa carga. Sharding desde o início. Equipe precisa ter expertise distribuída. Custo de complexidade compensa pelo escalar — a alternativa simplesmente não existe.

Reconhecer em qual cenário o sistema atual está, e em qual ele provavelmente vai estar em 2 anos, é trabalho de design.

Stack típica em três escolhas

Para fixar como vertical e horizontal aparecem em stacks diferentes, considere o setup de cada ecossistema para uma API moderna em 2026.

C# — ASP.NET Core horizontal por construção
# estrutura típica em Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-api
spec:
  replicas: 3                    # horizontal: 3 réplicas mínimas
  template:
    spec:
      containers:
      - name: api
        image: catalog-api:latest
        resources:
          requests: { cpu: 250m, memory: 256Mi }
          limits:   { cpu: 1000m, memory: 1Gi }
        readinessProbe:
          httpGet: { path: /health/ready, port: 8080 }
        livenessProbe:
          httpGet: { path: /health/live, port: 8080 }
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: catalog-api-hpa
spec:
  scaleTargetRef:
    name: catalog-api
  minReplicas: 3
  maxReplicas: 50              # autoscaling até 50 réplicas
  metrics:
  - type: Resource
    resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }

ASP.NET Core sem estado em memória (sessão em Redis, dados em banco). Kubernetes HPA escalona baseado em CPU; pode-se usar KEDA para escalar por queue depth ou métrica custom.

Python — FastAPI horizontal + workers verticais
# API horizontal (mesma ideia do K8s)
# Workers separados também horizontais para CPU bound, com Gunicorn

# gunicorn config para workers (1 processo, várias threads)
# workers = 1 (horizontal, K8s replica)
# threads = 8 (vertical dentro do pod, asyncio é single-thread)
# worker_class = "uvicorn.workers.UvicornWorker"

# app stateless
@app.get("/api/produtos/{id}")
async def get_produto(id: str):
    # estado vai para Redis/banco — nunca em memória local
    return await repo.obter(id)

# para ML inference que precisa de GPU + modelo grande
# escolha vertical: 1 pod com GPU grande, modelo carregado uma vez
# resources:
#   limits: { nvidia.com/gpu: 1 }
#   requests: { memory: 32Gi }

Python tem nuance extra: GIL impede paralelismo de CPU em uma instância (módulo 04). Solução típica: vários workers (processos separados) por pod, ou pods separados. ML inference é a exceção onde GPU vertical ainda ganha.

Go — horizontal default, scaling natural
// Go aproveita melhor uma máquina única que Python
// (sem GIL) — mas mesmo assim, horizontal é o default

// num pod K8s típico:
// resources:
//   requests: { cpu: 500m, memory: 256Mi }
//   limits:   { cpu: 2000m, memory: 1Gi }
// goroutines escalam dentro do pod até GOMAXPROCS

// stateless por construção
func (h *Handler) GetProduto(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    // tudo via repositório externo, nunca em memória local
    p, err := h.repo.Obter(r.Context(), id)
    if err != nil { http.NotFound(w, r); return }
    json.NewEncoder(w).Encode(p)
}

// Go shines em sistemas com alto tráfego, baixa
// memória por request — milhares de goroutines por pod,
// dezenas de pods em K8s, throughput total em milhões RPS

Go aproveita bem hardware: alto throughput por pod, baixa memória, scaling tanto vertical (múltiplos cores via GOMAXPROCS) quanto horizontal (vários pods). É linguagem onde horizontal default é mais econômico.

Anti-padrões frequentes

Scale out cedo demais em time inexperiente. Time pequeno adota Kubernetes multi-cluster, sharding, multi-region antes de precisar. Complexidade operacional consume mais tempo do que feature work. Defesa: começar simples; escalar quando dor real aparecer.

Scale up tarde demais sob crise. Sistema sob carga começa a degradar; time tenta resolver com refactor para horizontal sob pressão. Bug e downtime. Defesa: monitorar utilization sustentada; agir antes de saturar.

Misturar horizontal e estado em memória. 5 réplicas de aplicação, cada uma mantendo cache local diferente. Usuário recebe resposta inconsistente entre requests. Defesa: stateless rigoroso — estado em Redis/banco/fila.

Scale up até overspending. Migração cega "vamos para a maior instância" quando o problema é query lenta ou índice faltando. Defesa: profile antes de escalar (módulo 06).

Horizontal sem load balancer decente. 10 réplicas atrás de DNS round-robin (sem health check, sem rebalancing). Quando uma cai, 10% dos usuários veem erro até DNS atualizar. Defesa: load balancer real (ALB, GCP LB, NGINX) com health check ativo.

armadilha em produção

Sistema horizontal com estado em memória local descoberto sob auto-scaling. Cenário: aplicação tem cache in-memory de configurações. Funciona com 2 réplicas (ambas tipicamente sincronizadas pelo deploy). Auto-scaling adiciona 3ª réplica em pico de tráfego — ela tem cache vazio, comportamento ligeiramente diferente. Em deploy posterior, configuração muda; 2 réplicas antigas têm cache da config antiga, 1 nova tem da nova. Usuários veem comportamento aleatório por algumas horas. Defesa: stateless é regra inegociável em sistemas com auto-scaling. Cache local apenas para dados verdadeiramente imutáveis (lookup tables que nunca mudam).

heurística do sênior

Antes de qualquer decisão de scaling, articule cinco perguntas. "Qual o tráfego atual e qual o esperado em 6/12/24 meses?". "A aplicação é stateless ou tem estado em memória que precisa sincronizar?". "Que % do custo é hardware vs operação?". "Quem opera isso — time grande com SRE, ou squad pequeno?". "Que SLA o sistema precisa entregar?". As respostas filtrar a maioria das decisões — sistema novo, time pequeno, SLA modesto, cresce devagar: scale up, simples; sistema com tráfego variável, time experiente, SLA agressivo: horizontal desde o início; sistema com banco gigante, ACID exigente: vertical no banco até doer.

Por que importa para a sua carreira

A escolha de scale up vs scale out aparece em quase toda discussão de design de sistema com pretensão de crescer. Em entrevistas, "como você dimensionaria a infraestrutura desse sistema?" é convite para mostrar maturidade — a resposta forte considera tráfego previsto, complexidade operacional, SLA, e time. Em revisão de proposta de arquitetura, ver time pequeno sobreengenheirando para "Web scale" e propor "comece simples, scale up até doer" é serviço ao time. Em pos-mortem de sistema que "não escalou", diagnosticar se o problema era ausência de horizontalização ou apenas instância sub-dimensionada guia a correção. Em discussão de custo de cloud, traduzir "vamos crescer 10×" em "precisamos passar de scale up para scale out, e isso muda a topologia toda" articula o trade-off certo.

Como praticar

  1. Auditoria de dimensionamento em projeto seu. Pegue um projeto seu que está em produção. Articule: o sistema é horizontal ou vertical hoje? Qual o utilization sustentado de CPU/memória/IO? A próxima ordem de magnitude de tráfego cabe (vertical) ou exige refatoração (horizontal)? Documente em ADR. Esse exercício é diferencial em discussões com líderes técnicos.
  2. Cálculo de custos comparado. Para um sistema seu (ou um cenário), calcule o custo cloud de operar com: (a) 1 instância grande com replica passiva; (b) frota de 10 instâncias modestas atrás de load balancer com auto-scaling. Compare custos para tráfego médio e tráfego em pico (10×). Considere quem ganha quando. Esse cálculo, feito uma vez, calibra intuição sobre economia de scale.
  3. Identificar estado em memória que impede horizontal. Em um projeto seu, faça grep por static, variáveis globais mutáveis, caches in-memory. Para cada um, articule: se eu escalonar para 10 réplicas, qual o comportamento? Identifique pelo menos 1 instância de "estado em memória que viola stateless" e proponha refatoração para externalizar. Essa é uma das auditorias que mais rende em projetos jovens.

Referências para aprofundar

  1. livro The Datacenter as a Computer (3ª ed.) — Luiz André Barroso, Urs Hölzle, Parthasarathy Ranganathan (Morgan & Claypool, 2018). research.google/pubs/pub45406 — Gratuito. Os arquitetos do Google explicam por que commodity hardware horizontal venceu mainframe vertical no datacenter moderno. Fundador.
  2. livro Designing Data-Intensive Applications — Martin Kleppmann (O'Reilly, 2017). Cap. 6 (Partitioning) e cap. 5 (Replication) cobrem os trade-offs em profundidade. Indispensável para qualquer um que toca escalabilidade.
  3. livro Web Scalability for Startup Engineers — Artur Ejsmont (McGraw-Hill, 2015). Cap. 3 e 4 articulam scale up vs scale out em contexto de startup com pragmatismo. Cobertura prática boa para sêniores tomando decisões reais.
  4. livro Site Reliability Engineering — Beyer et al., Google (O'Reilly, 2016). Cap. 19 e 22 cobrem capacity planning e cascading failures em sistemas distribuídos. Gratuito em sre.google/books.
  5. livro Building Microservices (2ª ed.) — Sam Newman (O'Reilly, 2021). Cap. 13 (Scaling) cobre as decisões em microsserviços, incluindo quando vertical ainda ganha. Newman é um dos autores mais articulados sobre o trade-off.
  6. livro The Art of Capacity Planning (2ª ed.) — Arun Kejariwal, John Allspaw (O'Reilly, 2017). Atualização do clássico de Allspaw. Cap. 4 e 6 cobrem decisões de scale com base em dados de Twitter, Etsy, e outros casos reais.
  7. artigo Scalability! But at what COST? — Frank McSherry, Michael Isard, Derek Murray (HotOS, 2015). research.microsoft.com — Provocador. Argumenta que muitos sistemas distribuídos famosos seriam superados por uma única máquina bem otimizada. Vale ler para calibrar entusiasmo por horizontal.
  8. artigo Use One Big Server — Mark Litwintschik (blog, 2024). tech.marksblogg.com — Provocador. Argumenta que scale up moderno (servidor com 192 cores, 6 TB RAM) cobre necessidade de muitos sistemas que adotam horizontal preventivamente.
  9. artigo Scaling at AWS — Various (AWS Architecture Blog). aws.amazon.com/blogs/architecture — Casos reais de escolhas de scale em sistemas AWS. Útil para sentir como o time da AWS pensa.
  10. docs Kubernetes Horizontal Pod Autoscaler. kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale — A primitiva de scale out em K8s. Documentação canônica.
  11. docs AWS Auto Scaling. docs.aws.amazon.com/autoscaling — Equivalente AWS. Inclui Predictive Scaling moderno.
  12. vídeo How to Scale Apps to Millions of Users — várias palestras de AWS re:Invent (2018+). YouTube. AWS publica anualmente palestras sobre arquitetura para escala. Vale procurar pelas mais recentes.