Em 2009, a AWS lançou o Auto Scaling Group (ASG): um mecanismo para automaticamente subir e descer instâncias EC2 conforme demanda. A ideia era simples e revolucionária: em vez de provisionar capacidade para o pico (caro, ocioso na maior parte do tempo), o sistema ajusta capacidade dinamicamente. Pico de tráfego → mais instâncias; tráfego cai → menos instâncias. Pagar pelo uso real, não pelo planejado. A promessa de "elasticidade" da cloud virou concreta.
Quinze anos depois, auto-scaling é primitiva. Kubernetes tem HPA (Horizontal Pod Autoscaler) desde 2016; KEDA (Kubernetes Event-Driven Autoscaling) generaliza em 2019; AWS, GCP, Azure todos têm equivalentes nativos. A maioria das aplicações modernas em cloud opera com auto-scaling em produção. Mas configurá-lo bem — para que ajuste acompanhe carga sem oscilar, sem causar incidentes, e respondendo às métricas certas — continua sendo arte de operação.
Este conceito articula auto-scaling como disciplina. Métricas que disparam scaling (CPU, RPS, queue depth, custom), modos (reactive vs predictive), o problema clássico de scale-down (mais difícil que scale-up), oscilação (flapping), e os anti-padrões comuns. O foco é prático — configurações que sobrevivem a produção real, não apenas conceito teórico.
A premissa do auto-scaling é que o serviço é stateless (conceito 02 do módulo). Sem stateless, adicionar/remover instâncias gera bug ou perda de dado. Com stateless, é mecânica que pode ser configurada confiavelmente.
Métricas — qual usar para disparar scaling
Auto-scaling opera por métrica. Você define um alvo (target) para alguma métrica; o sistema ajusta o número de instâncias para manter a métrica próxima do alvo. A escolha da métrica define qualidade do scaling.
CPU utilization
Métrica mais clássica. Alvo típico: 60-70%. Quando média de CPU sobe, escala up; quando cai, escala down. Implementação trivial em qualquer cloud provider; dado disponível por default.
Ganhos: simples, sempre disponível, indicador correlacionado com carga. Limitações: para workloads I/O-bound, CPU baixo não significa folga (latência alta com CPU baixo). Workloads burst (picos curtos) podem terminar antes do scaling reagir.
Request rate (RPS)
Mais direto: escalar com base em quantas requests estão chegando. Alvo: capacidade conhecida por instância (ex.: 100 RPS por instância) × N instâncias = capacidade total. Quando RPS crescer, adicionar instâncias.
Ganhos: correlação direta com carga; antecipa CPU saturando. Limitações: capacidade por instância varia com tipo de request; precisa medir e calibrar; nem todos providers expõem trivialmente.
Queue depth
Para workers consumindo de fila (Kafka, SQS, RabbitMQ). Métrica: quantas mensagens não processadas estão na fila. Alvo: lag baixo (mensagens consumidas em janela de tempo aceitável).
Ganhos: ideal para workers — a métrica é exatamente o que precisa ser drenado. KEDA tornou trivial em Kubernetes. Limitações: não aplicável a APIs síncronas. Pode oscilar se mensagens chegam em bursts.
Latência (P99)
Escalar quando P99 ultrapassa alvo (ex.: P99 > 200ms por 5 minutos). Métrica de saúde direta — escalar quando experiência degrada.
Ganhos: correlação com SLO; só escala quando precisa. Limitações: latência pode ter múltiplas causas (banco lento, dependência externa) — escalar instâncias não resolve. Pode ser reativo demais.
Custom metrics
Métricas próprias do domínio: número de usuários online, tamanho do job atual, threads ativos. Kubernetes HPA via Prometheus Adapter; ASG via CloudWatch custom metrics.
Ganhos: precisamente alinhado com o que importa para o sistema. Limitações: complexidade de configurar pipeline de métrica; depende de observabilidade madura.
Times maduros tipicamente combinam: CPU como métrica primária + queue depth ou latência como secundária. KEDA tem ScaledObjects que aceitam múltiplos triggers.
Reactive vs predictive
Há duas filosofias de auto-scaling, com perfis complementares.
Reactive: ajusta com base em métrica corrente. CPU passou de 70%? Escala up. Voltou abaixo? Escala down. Simples, robusto, adapta a qualquer carga. Limitação: tem atraso. Métrica precisa cruzar threshold (alguns minutos), decisão é tomada, instância provisionada (1-3 minutos), entra no pool, começa a receber tráfego (alguns minutos). Total: 3-8 minutos para reagir. Em pico abrupto, sistema degrada antes de scaling reagir.
Predictive: antecipa carga com base em histórico ou eventos conhecidos. Padrão diário (carga sobe às 9h da manhã) detectado e escala up às 8:50. Black Friday agendada — escala preventivamente. AWS Predictive Scaling usa machine learning para prever; Kubernetes KEDA tem cron-based scaling.
Ganhos: zero latência reativa — capacidade já está pronta. Limitações: previsão pode estar errada (over-provisiona ou sub-provisiona); requer histórico ou conhecimento prévio.
O padrão maduro combina: predictive para padrões conhecidos (diário, semanal, eventos previstos) + reactive para o resto. Scale-up é predictivo quando há padrão; reativo quando há surpresa. Scale-down é tipicamente sempre reactive (com cooldown adequado).
Scale-up vs scale-down — assimetria
Scaling tem assimetria fundamental: scale-up é seguro, scale-down é arriscado. A consequência é que configurações maduras tratam os dois diferentemente.
Scale-up agressivo: quando carga cresce, melhor escalar rápido demais que tarde. Custo de instância extra por alguns minutos é barato; custo de degradação por sub-provisionamento é caro. Configurações típicas: ajuste por janela curta (1-3 min), múltiplas instâncias por step.
Scale-down conservador: quando carga cai, melhor descer devagar. Razões:
- Carga pode subir de novo logo (oscilação).
- Cache local na instância removida é perdido (próximas requests para outras instâncias têm miss).
- Conexões em curso na instância removida precisam terminar (graceful shutdown).
- Provisionar instância nova depois custa minutos.
Configurações típicas: cooldown de 5-15 min antes de tentar scale-down após scale-up; ajuste em janela longa (5-15 min); poucas instâncias por step. Algumas equipes preferem scale-down manual em horários conhecidos (final do dia) em vez de automático.
Flapping — o problema da oscilação
Auto-scaling mal configurado pode oscilar: escalar up, escalar down, repetir. Flapping é o nome desse comportamento, e gera vários problemas:
Custo aumentado. Provisionar instância tem custo de startup (em cloud, ~1 minuto). Se sistema escala +10 e -10 em ciclos de 5 minutos, esse custo é desperdiçado.
Cache cold em ciclo. Cada nova instância começa com cache vazio. Se está sempre adicionando/removendo, cache nunca esquenta de verdade.
Conexões cortadas. Em scale-down, conexões ativas precisam terminar (graceful) ou são cortadas (não-graceful). Flapping força isso repetidamente.
A defesa contra flapping é configurar thresholds com hysteresis: scale-up em uma threshold (CPU > 70%), scale-down em threshold mais baixa (CPU < 40%). Janela entre 40-70% é zona de "não mexer". Combine com cooldown adequado.
Kubernetes HPA tem stabilizationWindowSeconds
explicitamente para isso. AWS ASG tem cooldown
period. Configurar bem evita oscilação.
Cold start — o custo de instância nova
Quando auto-scaling adiciona instância, ela precisa: ser provisionada (cloud), inicializar runtime (JVM/CLR/Python), carregar dependências, conectar a banco/cache, possivelmente warm-up de cache. Tempo total: cold start.
Tipos de cold start:
Provisionamento de máquina (1-3 min). Em ASG/Kubernetes node pools, criar nova VM. Pode ser eliminado com pools pré-aquecidos ou serverless.
Container start (10-60 s). Pull de imagem, montagem, init. Pode ser reduzido com imagens otimizadas (distroless, multi-stage builds).
Application startup (5-30 s). JVM JIT warmup, .NET tiered compilation, Python imports. Pode ser melhorado com AOT compilation, GraalVM native-image, .NET Native AOT.
Cache warmup (variável). Se a app tem cache local, atingir hit rate alto leva minutos. Em pior caso, instância nova é mais lenta por horas.
Soma típica: 1-5 minutos de cold start. Para auto-scaling reagir a pico abrupto, esse atraso é o que limita responsividade. Sistemas com SLO agressivo precisam ou (a) capacidade reserva sempre (mais cara), ou (b) predictive scaling, ou (c) cold start otimizado.
HPA do Kubernetes — caso canônico
O HPA é o padrão de auto-scaling em Kubernetes (cluster-wide, múltiplos providers). Vale ver configuração real.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: catalog-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: catalog-api
minReplicas: 3 # nunca menor que 3 (HA)
maxReplicas: 50 # custos: cap superior
metrics:
# CPU como métrica primária
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# custom metric (RPS) via Prometheus adapter
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # reage em 30s
policies:
- type: Percent
value: 100 # pode dobrar
periodSeconds: 60
- type: Pods
value: 5 # ou +5 instâncias
periodSeconds: 60
selectPolicy: Max # qual mais agressiva
scaleDown:
stabilizationWindowSeconds: 300 # espera 5 min antes de scale-down
policies:
- type: Percent
value: 10 # remove no máximo 10% por vez
periodSeconds: 60
Note três aspectos importantes do exemplo. Primeiro: múltiplas métricas (HPA escolhe a que pede mais instâncias). Segundo: comportamento assimétrico (scale-up agressivo, scale-down conservador). Terceiro: limites mín/máx — sem máximo, custo pode explodir; sem mínimo, ficamos vulneráveis a queda.
KEDA — auto-scaling event-driven
KEDA (Kubernetes Event-Driven Autoscaling, lançado 2019, CNCF graduated 2023) estende HPA para escalar baseado em qualquer evento externo. Suporta 60+ "scalers": Kafka, RabbitMQ, SQS, Redis, Prometheus, Postgres, custom.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: notification-worker
spec:
scaleTargetRef:
name: notification-worker
minReplicaCount: 0 # KEDA pode escalar para 0!
maxReplicaCount: 100
pollingInterval: 30 # checa a cada 30s
cooldownPeriod: 300 # espera 5 min antes de zerar
triggers:
- type: kafka
metadata:
bootstrapServers: kafka:9092
topic: notifications
consumerGroup: notification-workers
lagThreshold: "1000" # escala se lag > 1000
offsetResetPolicy: latest
- type: cpu
metadata:
type: Utilization
value: "70"
KEDA aceita múltiplos triggers; usa o mais
restritivo. minReplicaCount: 0 permite
zerar workers quando não há tráfego — economia
significativa para workloads bursty (job batch
noturno, eventos esporádicos). Quando primeira
mensagem chega, KEDA acorda 1 worker; daí escala
conforme lag cresce.
Auto-scaling em três stacks
Cada ecossistema tem padrão idiomático para auto-scaling.
// Program.cs — preparar app para auto-scaling
var builder = WebApplication.CreateBuilder(args);
// readyz/livez probes
builder.Services.AddHealthChecks()
.AddNpgSql(connStr, name: "postgres", tags: new[] { "ready" })
.AddRedis(redisConn, name: "redis", tags: new[] { "ready" });
var app = builder.Build();
app.MapHealthChecks("/livez");
app.MapHealthChecks("/readyz", new HealthCheckOptions {
Predicate = h => h.Tags.Contains("ready"),
});
// graceful shutdown — 30 s para drenar
app.Lifetime.ApplicationStopping.Register(() =>
{
// ready falha primeiro, balancer remove
// espera in-flight requests terminarem
Thread.Sleep(2000);
});
// custom metric exportada (RPS via Prometheus)
builder.Services.AddSingleton<Counter>(_ =>
Metrics.CreateCounter("http_requests_total", "Total HTTP requests"));
# K8s deployment com resources adequadas para HPA decidir
# resources:
# requests: { cpu: 250m, memory: 256Mi } # base para HPA target
# limits: { cpu: 1000m, memory: 1Gi }
# terminationGracePeriodSeconds: 30 # para drenar
# preStop hook: sleep 5 (espera balancer remover)
ASP.NET Core integra com K8s via probes, e HPA escala baseado em CPU + RPS via Prometheus adapter. Pontos críticos: probes corretos, resources requests calibradas, graceful shutdown configurado.
# Celery worker que processa fila Redis
from celery import Celery
app = Celery("worker", broker="redis://redis:6379/0")
@app.task
def processar_notificacao(payload):
# processa notificação
return enviar(payload)
# K8s deployment para o worker
# replicas: 1 (KEDA controla)
# ScaledObject KEDA para escalar conforme fila
# scaleTargetRef: deployment do worker
# triggers:
# - type: redis
# metadata:
# address: redis:6379
# listName: celery
# listLength: "100" # escala se > 100 mensagens
# minReplicaCount: 1 # mínimo 1 (cold start fica longo se 0)
# maxReplicaCount: 50
# graceful shutdown — Celery captura SIGTERM e termina jobs em curso
# soft_time_limit / time_limit limitam jobs longos
Celery + KEDA é stack canônica para workers Python escalonáveis. KEDA monitora fila Redis, escala workers conforme lag. minReplicaCount=1 evita cold start; pode ser 0 para workloads esporádicos.
// Go worker que consome Kafka
package main
import (
"context"
"github.com/segmentio/kafka-go"
)
func main() {
reader := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{"kafka:9092"},
Topic: "notifications",
GroupID: "notification-workers",
})
defer reader.Close()
ctx, cancel := signal.NotifyContext(context.Background(),
os.Interrupt, syscall.SIGTERM)
defer cancel()
for {
msg, err := reader.FetchMessage(ctx)
if err != nil { return }
if err := processar(ctx, msg); err != nil { continue }
reader.CommitMessages(ctx, msg)
}
}
# ScaledObject KEDA para Kafka lag
# triggers:
# - type: kafka
# metadata:
# bootstrapServers: kafka:9092
# topic: notifications
# consumerGroup: notification-workers
# lagThreshold: "1000"
# minReplicaCount: 1
# maxReplicaCount: 50
# advanced:
# horizontalPodAutoscalerConfig:
# behavior:
# scaleDown:
# stabilizationWindowSeconds: 300
# Go é especialmente bom para workers escalonáveis:
# - cold start rápido (~200ms para AOT)
# - baixo footprint de memória
# - paralelismo nativo (goroutines)
Go shines em workers escalonáveis: cold start rápido, baixa memória, paralelismo nativo. Combinado com KEDA, escala bem para milhares de workers em pico e zera para 1 em ociosidade.
Anti-padrões frequentes
Auto-scaling sem stateless adequado. Já visto. Conceito 02. Sistema com cache local, sticky sessions, ou conexões persistentes mal coordenadas vai sofrer com auto-scaling. Defesa: stateless rigoroso.
Métrica errada. CPU como métrica primária para workload I/O-bound; ou RPS sem considerar request heterogeneity. Defesa: medir antes; entender qual métrica melhor reflete capacidade saturada.
Thresholds simétricos. Scale-up e scale-down em mesma threshold (ex.: 70%). Causa flapping. Defesa: hysteresis (scale-up 70%, scale-down 40%).
Cooldown insuficiente. 1 minuto entre eventos de scaling. Não dá tempo para capacidade nova entregar valor; oscila. Defesa: cooldown de 3-5 min para scale-up, 10-15 min para scale-down.
maxReplicas sem teto significativo. Em incidente (DDoS, bug que faz queries lentas), auto-scaling escala para 1000 instâncias antes de time perceber. Custo explode; banco satura. Defesa: maxReplicas razoável (50-100), com alerta quando atinge.
minReplicas baixo demais.
minReplicas: 1 em produção crítica.
Quando essa única instância falha, downtime total.
Defesa: minReplicas: 3 ou mais para
HA — três instâncias permitem perder uma sem
degradar.
Não testar comportamento sob auto-scaling. Configura, deixa rodando, espera funcionar. Em produção, descobre que cold start é mais lento que esperado, ou que cache local faz instâncias novas lentas. Defesa: testes de spike em staging simulando o pior cenário.
Cascade failure por auto-scaling. Banco fica ligeiramente lento; latência das instâncias da aplicação sobe; HPA escala mais instâncias para compensar; novas instâncias abrem mais conexões ao banco; banco fica mais lento; HPA escala ainda mais; banco fica praticamente parado; sistema entra em colapso. Defesa: connection pool bem dimensionado (não cresce com instâncias); circuit breaker em torno do banco; alerta em pool utilization alta; rate limit antes de auto-scaling agressivo. Auto-scaling pressupõe que o gargalo é a aplicação. Quando o gargalo é dependência downstream, auto-scaling agrava o problema.
Antes de configurar auto-scaling, articule cinco coisas. "Qual a métrica que melhor reflete saturação?" Se a resposta é "CPU porque é default", está cedo demais. "Qual o cold start típico?" Se é >5 min, predictive scaling pode ser obrigatório. "Qual o min/max razoável?" Min para HA; max para evitar custo descontrolado. "Qual o behavior assimétrico de scale up/down?" Articulado antes de produção. "Como o sistema se comporta se o gargalo é downstream?" Auto-scaling agrava — circuit breaker e rate limit são defesas necessárias. Quem articula esses 5 pontos antes de configurar evita os incidentes mais comuns.
Por que importa para a sua carreira
Auto-scaling é configuração cotidiana em sistemas cloud-native. Em entrevistas, "como você escalonaria essa aplicação automaticamente?" é convite para articular métrica, modo, behavior. A resposta forte cita assimetria scale-up/down, hysteresis, cold start, e considera o cenário de cascade failure. Em revisão de configuração K8s, perceber HPA mal-configurado (thresholds simétricos, maxReplicas alto demais) é serviço ao time. Em pos-mortem de "auto-scaling escalou para 200 instâncias e quebrou banco", articular que o problema é missing rate limit + circuit breaker é trabalho de senior. Em discussão de capacity planning, articular auto-scaling como ferramenta complementar (não substituta) de planning é vocabulário maduro.
Como praticar
- HPA local em minikube/kind. Configure aplicação simples em K8s local com HPA baseado em CPU. Use ferramenta de carga (k6) para gerar tráfego variável; observe HPA escalando. Modifique thresholds, behavior; observe efeitos. Esse exercício, feito uma vez, fixa muitos conceitos.
- KEDA com queue. Suba RabbitMQ ou Kafka local; aplicação que consome; KEDA configurado para escalar baseado em lag. Envie burst de mensagens; observe KEDA escalando workers. Compare com HPA por CPU. Esse exercício é valioso especialmente para sistemas com workers.
- Spike test com auto-scaling. Em ambiente staging com auto-scaling configurado, execute teste com k6 que aumenta carga 10× em 30 segundos. Meça: tempo para HPA detectar, tempo para nova instância entregar tráfego, tempo até latência estabilizar. Documente a "janela de degradação" — o tempo entre pico e capacidade restaurada. Esse é métrica que sêniores operam em sistemas críticos.
Referências para aprofundar
- docs Kubernetes HPA.
- docs KEDA Documentation.
- docs AWS Auto Scaling.
- docs GCP Managed Instance Groups Autoscaling.
- livro Kubernetes Patterns — Bilgin Ibryam, Roland Huß (O'Reilly, 2019).
- livro Site Reliability Engineering — Beyer et al., Google (O'Reilly, 2016).
- livro The Art of Capacity Planning (2ª ed.) — Allspaw & Kejariwal (O'Reilly, 2017).
- artigo Predictive Auto Scaling at AWS — Various AWS Architecture Blog posts (2019+).
- artigo Lessons from Six Years of Production Use of Kubernetes — Spotify Engineering (várias publicações).
- artigo How Discord Migrated to Kubernetes — Discord Engineering (blog).
- artigo Adopting KEDA: How Microsoft uses event-driven autoscaling — Microsoft Engineering (várias publicações).
- vídeo Cluster Autoscaling and Pod Autoscaling — KubeCon talks (vários anos).