Lab 06 — Escalar quando chega gente de verdade
O problema, e a empresa que o tem
A Cadência é a mesma equipe de duas pessoas do L01 e do L03: uma API .NET 8 de pedidos e catálogo em ECS Fargate, banco em sub-rede privada, balanceador na frente. Depois do L03 ela publica várias vezes por dia sem janela. O que este laboratório muda é o que acontece quando o número de clientes muda.
Em 5 de setembro as trinta lojas fazem a campanha de aniversário. O e-mail e o push saem juntos, às 19h. No ano passado, em 90 segundos a chegada passou de 25 para 260 requisições por segundo, o p99 subiu de 400 ms para 9 s, o balanceador começou a devolver 504 e o carrinho de quem clicou primeiro não abriu. A campanha custou o que se pagou por ela e converteu uma fração do que devia.
A discussão que se seguiu é a que dá título a este laboratório: alguém propôs subir para 10 tasks, outra pessoa disse que 10 é pouco, e a decisão saiu por consenso em 8. Ninguém sabia dizer por que 8 e não 20, porque ninguém sabia quanto uma task aguenta. A pergunta "qual número subir" não tem resposta certa — ela tem uma resposta anterior, que é qual métrica olhar.
Existe escala automática configurada, e é isso que torna o caso interessante. Está lá, com target tracking em CPU a 70%, exatamente como o tutorial ensina. Ela não disparou nenhuma vez durante a campanha. O painel mostra CPU plana em 34% e latência em despenhadeiro no mesmo eixo de tempo, e essa figura é o assunto do módulo.
O que este laboratório NÃO é
Não é teste de carga até a quebra, e não é caça ao gargalo. Aqui a carga serve a um propósito estreito: descobrir o teto de UMA task e verificar se a métrica candidata é proporcional à capacidade. Empurrar a arquitetura até romper e nomear o componente que rompeu é o L40, que depende deste. Também não é escalar o banco: quando as tasks multiplicam conexões, este módulo calcula o teto e para ali — réplica e pool são o L07, cache é o L13.
O que você vai conseguir fazer
Cada objetivo se prova com um comando na seção de implantação. Nenhum deles é "entender escala automática".
- Aplicar o teste de proporcionalidade a uma métrica candidata e reprovar a CPU com número, não com opinião.
- Explicar por que `ALBRequestCountPerTarget` é antecedente e proporcional por construção, e citar quem faz a divisão.
- Somar os quatro atrasos entre a chegada da carga e a chegada da capacidade, e dizer qual deles você consegue reduzir.
- Derivar o valor do alvo do teto medido por task, com a unidade correta — por minuto, por alvo.
- Derivar o `max_capacity` do teto de conexões do banco, em vez de escolher um número confortável.
- Decidir entre target tracking e step scaling a partir da forma da subida, e dizer o que o step scaling obriga você a reconstruir à mão.
- Configurar escala programada para o pico com data e justificar por que ela é mais confiável que qualquer política reativa.
- Reproduzir a rampa da campanha com carga e produzir o gráfico do evento como arquivo.
- Diagnosticar escala automática que não dispara, distinguindo três causas pelo sintoma.
- Explicar por que a descida é deliberadamente mais lenta que a subida, nos dois tipos de política.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Target tracking vs step scaling | SAA-C03, SOA-C02 | target tracking como regime, step só para o degrau | que target tracking é proporcional e traz proteção contra oscilação embutida |
| Escolha da métrica de escala | SAA-C03, SOA-C02 | CPU reprovada por teste, chegada aprovada | as duas propriedades exigidas: correlação com a demanda e proporcionalidade à capacidade |
| `ALBRequestCountPerTarget` | SAA-C03, DVA-C02 | métrica de regime, com `ResourceLabel` | que a unidade é por minuto por alvo, e que o rótulo é a concatenação dos dois sufixos de ARN |
| Cooldown de subida e de descida | SOA-C02 | 60 s para subir, 300 s para descer | que os dois têm intenções opostas: subir continuamente, descer com relutância |
| Escala programada | SAA-C03, SOA-C02 | piso elevado 15 min antes da campanha | que ela mexe nos LIMITES e não no desejado, e que dispara mesmo durante cooldown |
| Escala preditiva | SAA-C03, SAP-C02 | avaliada e recusada, com motivo | que exige no mínimo 24 h de histórico, que sozinha não reduz capacidade, e que serve a padrão cíclico |
| Escala e implantação ao mesmo tempo | DVA-C02, DOP-C02 | a interação com o rollout do L03 | que o Application Auto Scaling desliga a DESCIDA durante a implantação e mantém a subida |
| Limite de capacidade a partir da dependência | SAP-C02 | `max_capacity` derivado de `max_connections` | que escalar uma camada transfere pressão para a de baixo, e o máximo é o menor teto |
| Métricas do ALB por alvo vs por balanceador | SOA-C02 | 504 do alvo contra 503 do balanceador | que latência e ausência de destino são sintomas de camadas diferentes |
Onde isto costuma ser cobrado errado
A questão clássica descreve uma aplicação que espera um serviço externo, mostra CPU em 30% durante a degradação e pergunta por que a escala automática por CPU não age. Quem decorou "target tracking em CPU a 70%" responde que o alvo está alto demais, e erra: baixar o alvo para 40% não resolve, porque a CPU não é proporcional à capacidade nessa aplicação — ela ficaria em 30% com o dobro de tasks. A resposta é trocar a métrica por uma de chegada ou de concorrência. A pergunta testa se você sabe que existe um critério para escolher métrica, não se você lembra de um número.
Requisitos, e como cada um muda o desenho
A coluna da direita é onde cada requisito deixou marca. Requisito que não aparece numa linha de configuração é intenção, e a campanha do ano passado tinha muitas.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| p99 durante a campanha | abaixo de 800 ms | define o teto por task no teste de carga: 30 req/s é onde o p99 chega a 780 ms |
| Erro visível ao cliente | abaixo de 0,1% | obriga folga de um terço no alvo (1.200 de 1.800), para o atraso não virar erro |
| Forma da subida | rampa de 90 s, 10× o regime | reprova depender só de política reativa: o atraso medido é maior que a rampa |
| Data do pico | conhecida com uma semana de antecedência | autoriza ação programada, que é a peça sem atraso — e a mais barata das três |
| Pico não previsto | pode acontecer, sem data | mantém target tracking por cima do piso programado; sem ele, o excesso não tem resposta |
| Subida súbita em degrau | observada quando um influenciador publica | justifica a política de step scaling num alarme próprio, com margem sobre o alvo |
| Falha de AZ no pico | não pode cortar a capacidade pela metade | piso de 4 tasks em duas AZs, não 2 — o mínimo do regime não é o mínimo da resiliência |
| Teto de conexões do banco | lido do grupo de parâmetros, não estimado | define `max_capacity` em 40, com pool Npgsql fixado em 10 por task |
| Custo fora da campanha | não pagar pico o mês inteiro | reprova superprovisionar fixo; a ação programada que DESCE é obrigatória, não opcional |
| Sem plantão noturno | requisito da equipe de duas pessoas | a descida tem de ser automática e conservadora, e o cooldown de descida fica em 300 s |
O requisito que mais gente esquece de declarar
A ação programada que DESCE o piso. Elevar capacidade antes da campanha é a parte que todo mundo lembra, porque ela tem urgência. Devolver o piso depois não tem urgência nenhuma — e é por isso que fica para depois e nunca acontece. Catorze tasks ligadas por um mês por causa de uma campanha de vinte minutos é o defeito de custo mais comum deste laboratório, e ele não gera nenhum alarme, porque nada está errado tecnicamente.
Arquitetura mínima: a escala automática que não escala
Este é o desenho que a Cadência tem, e ele é legítimo como ponto de partida: está no Terraform, tem escala automática de verdade e é o que qualquer tutorial de ECS produz. O laboratório começa por medir o que ele faz, porque "a escala não funcionou" não é um fato utilizável e "a política não disparou nenhuma vez em 20 minutos de pico" é.
- → 10× o tráfego em 90 s, sem aviso à plataforma
- → escolhe entre os alvos saudáveis
- → requisição de catálogo e de pedido
- → consulta que a task espera, sem gastar CPU
- → CPU por task, agregada em 1 minuto
- → ponto de métrica com até 60 s de atraso
- → UpdateService com desiredCount novo
- → parte task nova; ela ainda não recebe tráfego
- → update-service --desired-count 20, à mão
- Fora da AWS
- Rede e entrega
- Compute
- Banco de dados
- Gestão e governança
Este desenho tem escala automática ligada e cai no pico do mesmo jeito. O motivo não é um valor errado no alvo: é que a métrica escolhida mede o efeito em vez da chegada, e chega tarde. Percorra os passos e some os atrasos — a conclusão a levar é que capacidade só aparece depois de a métrica existir, e a métrica existe depois do pico.
- O pico chega antes de a métrica existir. A ECS coleta utilização a cada 20 s e publica UM ponto por minuto no CloudWatch. Se a rampa da campanha dura 90 s, o primeiro ponto que descreve o pico só existe quando mais da metade dele já passou. Nenhum ajuste de alvo muda isso: é o período da métrica.
- A CPU mede o efeito, e nesta aplicação ela nem sequer é proporcional. A AWS exige duas propriedades de uma métrica de escala: correlação com a demanda e proporcionalidade à capacidade — dobrar as tasks tem de reduzir a métrica pela metade. Uma task .NET que espera o banco consome pouca CPU sob carga alta, então a CPU média fica plana em 35% com 4 tasks e com 13. O alvo de 70% nunca é atingido, e a política não dispara nunca.
- A chegada está medida e ninguém lê. O grupo de destino já publica `RequestCountPerTarget`: a contagem de requisições do grupo dividida pelo número de alvos saudáveis. É a métrica antecedente — ela sobe no instante em que o cliente aparece, sem depender de a aplicação sentir dor. Neste desenho ela existe e não está ligada a nada.
- Quando a capacidade sai, ela ainda não serve. Entre o `UpdateService` e a primeira requisição atendida pela task nova passam a partida da task e os ciclos de health check do grupo de destino. São os dois atrasos que o L03 ajustou, e aqui eles reaparecem como parcela do tempo de escala — a mesma configuração, cobrada por outro motivo.
- O 504 é do alvo, e o log da aplicação está cheio. Diferente do 503 do L03, aqui existe alvo saudável: ele recebeu a requisição e demorou demais. O sintoma é latência, não ausência de destino, e é por isso que olhar `HTTPCode_ELB_5XX_Count` não acha nada e `TargetResponseTime` acha tudo.
- O operador entra no laço e o sistema passa a oscilar. Com a política parada, alguém sobe o número à mão. Agora há dois controladores com atrasos diferentes agindo sobre a mesma variável, e o resultado é capacidade em serra: sobe demais, o custo assusta, desce, o pico volta. Escalar à mão não é falta de disciplina: é a única alavanca que sobrou quando a métrica escolhida não fala.
Antes de mudar qualquer coisa, produza a linha de base. Este comando pega o histórico de atividades de escala da campanha passada e a série da CPU no mesmo intervalo. A lista vazia é o achado.
# Rode ANTES de mexer em nada. A saida daqui e o numero que torna o defeito
# discutivel — e o que impede a conversa de voltar para "sobe para 8".
PROJETO=ffv-lab
RES="service/${PROJETO}/${PROJETO}-api"
# 1. A politica disparou? Esta lista e o coracao do diagnostico.
aws application-autoscaling describe-scaling-activities \
--service-namespace ecs --resource-id "$RES" \
--query "length(ScalingActivities)" --output text
# Na Cadencia: 0. Vinte minutos de pico, zero atividade de escala.
# 2. E a CPU, no mesmo intervalo? Se ela nunca chegou ao alvo, a politica
# fez exatamente o que devia — e o defeito e a metrica, nao a politica.
aws cloudwatch get-metric-statistics --namespace AWS/ECS \
--metric-name CPUUtilization \
--dimensions Name=ClusterName,Value=$PROJETO Name=ServiceName,Value=${PROJETO}-api \
--start-time 2025-09-05T21:55:00Z --end-time 2025-09-05T22:30:00Z \
--period 60 --statistics Average Maximum \
--query "sort_by(Datapoints,&Timestamp)[].[Timestamp,Average,Maximum]" --output table
# Na Cadencia: media entre 31% e 36%, maximo de 41%. O alvo era 70%.
# 3. E a dor, no mesmo intervalo? Aqui esta o contraste que faz a figura.
aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB \
--metric-name TargetResponseTime \
--dimensions Name=LoadBalancer,Value=$LBSUF Name=TargetGroup,Value=$TGSUF \
--start-time 2025-09-05T21:55:00Z --end-time 2025-09-05T22:30:00Z \
--period 60 --extended-statistics p99 \
--query "sort_by(Datapoints,&Timestamp)[].[Timestamp,ExtendedStatistics.p99]" --output table
# Na Cadencia: 0,41 s antes, 9,2 s no pico. CPU plana, latencia 22x pior.Os dois 5xx do pico não são os dois 5xx do deploy
No L03 o inimigo era o 503, que vem do balanceador e significa ausência de alvo saudável — nenhuma requisição chegou à aplicação. Aqui o inimigo é o 504, que vem do alvo: havia destino, ele recebeu a requisição e demorou mais do que o tempo limite. O log da aplicação, que no L03 estava limpo, aqui está cheio — de requisições lentas. Quem procura `HTTPCode_ELB_5XX_Count` num incidente de capacidade não encontra nada e conclui que a plataforma estava bem.
Arquitetura para produção
Três mudanças estruturais, e nenhuma delas é aumentar um número. O sinal muda de dono, a capacidade se parte em reativa e planejada, e o máximo passa a ser derivado. Cada peça abaixo aponta para uma linha da tabela de requisitos; se você não conseguir apontar, a peça é adorno.
- → rampa de 10× em 90 s, com data conhecida
- → encaminha e conta requisição por alvo
- → requisição para alvo saudável
- → consulta por conexão do pool com teto declarado
- → RequestCountPerTarget: chegada ÷ alvos saudáveis
- → CPUUtilization a cada 20 s, como piso de segurança
- → DatabaseConnections, para o máximo não virar incidente
- → alarme gerenciado pela própria política
- → alarme com limiar que você escolheu e mantém
- → desiredCount proporcional à distância do alvo
- → degrau de +50% quando a chegada dobra o limiar
- → eleva MinCapacity para 14 antes da campanha
- → parte a task; partida e health check continuam contando
- Fora da AWS
- Rede e entrega
- Compute
- Banco de dados
- Gestão e governança
Três mudanças estruturais, e nenhuma delas é "um número maior". O sinal deixa de sair do serviço e passa a sair do grupo de destino; a capacidade se divide em reativa e planejada, porque pico com data marcada não se espera; e o máximo passa a ser derivado do teto de conexões do banco. Percorra os passos: cada peça nova aponta para uma linha da tabela de requisitos.
- O sinal muda de dono. A métrica passa a sair do grupo de destino, não do serviço. `ALBRequestCountPerTarget` sobe no instante da chegada, antes de qualquer fila se formar, e o próprio ALB a divide pelo número de alvos saudáveis — o que satisfaz a exigência de proporcionalidade por construção, sem depender do comportamento da aplicação.
- Alta resolução encurta o primeiro atraso, e só o primeiro. Com `resolutionSeconds = 20` na configuração de monitoramento do serviço, a utilização passa a existir a cada 20 s em vez de 60. É o atraso mais fácil de reduzir e o único que se compra com configuração. Os outros três continuam onde estavam, e é por isso que ele não resolve sozinho.
- Target tracking é o regime, e a assimetria é intencional. A política adiciona capacidade tão rápido quanto pode e remove com relutância: só desce quando a métrica cai bem abaixo do alvo, tipicamente mais de 10% abaixo, porque remover uma unidade que traria a métrica de volta acima do alvo produziria oscilação. Isso é proteção embutida, e quem troca target tracking por step scaling precisa reconstruí-la à mão.
- O degrau grande é o único trabalho do step scaling. Target tracking é proporcional: quanto mais longe do alvo, mais capacidade ele pede. Mas quando a chegada dobra o limiar de uma vez, um degrau fixo de +50% num alarme de um período responde antes. O step scaling não substitui o regime; ele cobre a cauda súbita, e a AWS avisa que misturar os dois sem margem entre os limiares produz conflito.
- Pico com data não se espera: eleva-se o piso antes. A ação programada não observa métrica nenhuma e por isso não tem atraso. Ela sobe o `MinCapacity` para as 14 tasks que o teste de carga mostrou necessárias, 15 minutos antes do disparo do e-mail. É mais barata que superprovisionar o mês inteiro e mais confiável que qualquer política reativa — e a política reativa continua ali, para o pico que a campanha não previu.
- O máximo é um teto de baixo, não um número de conforto. Quarenta tasks com pool de dez conexões são quatrocentas conexões no Postgres. O `max_connections` do grupo de parâmetros do RDS é derivado da memória da instância, e é ele quem decide o máximo do serviço. Escalar a camada de task sem esse cálculo transfere a queda para o banco e troca 504 por erro de conexão recusada.
- O gráfico do evento é o entregável, e a área entre as curvas é a nota. Três séries no mesmo eixo de tempo: chegada por alvo, alvos saudáveis e p99 de resposta. A área em que a chegada está acima do que a capacidade sustentava é exatamente a fatia de pico que o cliente engoliu. Sem esse gráfico, "a escala funcionou" é opinião.
A diferença estrutural não é a quantidade de caixas: é de onde vem o sinal. No desenho mínimo, a informação sai do serviço — a utilização das tasks, que é consequência. Aqui ela sai do grupo de destino, que é onde a chegada é contada antes de alguém sofrer. Todo o resto administra essa troca.
O ajuste com maior efeito por linha alterada, e ele já estava feito
Das quatro parcelas, a maior no padrão não é a métrica: é o health check do grupo de destino, com 5 verificações a cada 30 s, ou 150 s antes de a task nova receber a primeira requisição. Quem fez o L03 já baixou isso para 2 × 5 s e tem 10 s. Quem não fez vai medir um atraso de escala de mais de cinco minutos, atribuir à política e passar o dia ajustando alvo. O relógio que domina o rollout é o mesmo que domina a escala, e é uma linha no grupo de destino.
Da chegada da carga à chegada da capacidade
Os quatro atrasos não são jargão: são relógios diferentes, em objetos diferentes, e é por isso que quase nunca são lidos juntos. O da métrica está na configuração do serviço e no balanceador; o do alarme, na política; o da partida, na definição da task e no tamanho da imagem; o do health check, no grupo de destino.
O número que a AWS publicou, e por que ele confirma a leitura
Em 18 de junho de 2026, ao lançar as métricas de 20 segundos para a ECS, a AWS publicou o resultado do próprio teste comparativo: o tempo até disparar a subida caiu de 363 s para 86 s, e o tempo total até a capacidade nova estar provisionada caiu de 386 s para 109 s. Repare na proporção entre os dois pares: no cenário lento, quase todo o tempo estava ANTES da decisão — 363 dos 386 segundos. O gargalo da escala reativa é a observação, não o provisionamento, e é exatamente isso que a fórmula acima diz.
Vale explicitar o que muda e o que não muda. A resolução de 20 s encurta a primeira parcela da utilização, e apenas ela. A chegada pelo ALB continua sendo publicada em intervalos de 60 s, porque o intervalo é do balanceador e não do serviço. E o atraso da partida da task e o do health check não se movem com nenhuma configuração de métrica — eles são físicos.
// O que `describe-scaling-activities` devolve quando a politica age. Este e o
// objeto que responde "quando ela decidiu", e a diferenca entre o inicio da
// rampa e o `StartTime` daqui e o atraso total, medido.
{
"ActivityId": "e6c5f1a1-8d2e-4a44-9c31-7b0f2d9a4c88",
"ServiceNamespace": "ecs",
"ResourceId": "service/ffv-lab/ffv-lab-api",
"ScalableDimension": "ecs:service:DesiredCount",
"Description": "Setting desired count to 9.",
// A causa cita o alarme, o valor observado e o LIMIAR — e e por isso que
// ela vale mais que o painel: ela diz o que a politica enxergou.
"Cause": "monitor alarm TargetTracking-service/ffv-lab/ffv-lab-api-AlarmHigh-... in state ALARM triggered policy ffv-lab-api-chegada",
"StartTime": "2026-09-05T22:01:47.318Z",
"EndTime": "2026-09-05T22:01:49.902Z",
"StatusCode": "Successful",
// Repare: o EndTime e dois segundos depois. A politica termina aqui, e a
// CAPACIDADE nao chegou — faltam a partida da task e o health check. Medir
// escala por este campo e o erro que faz a escala parecer instantanea.
"StatusMessage": "Successfully set desired count to 9. Change successfully fulfilled by ecs."
}A escala e a implantação mexem na mesma variável
Durante uma implantação da ECS, o Application Auto Scaling desliga os processos de DESCIDA e mantém os de subida. A assimetria é deliberada e protege disponibilidade: reduzir capacidade no meio de uma troca de versão é a pior hora possível. A consequência prática vem do L03: `RequestCountPerTarget` divide pelo número de alvos saudáveis, e um rollout muda esse denominador sem que a demanda tenha mudado. Se você precisa que nada escale durante o deploy, existe caminho explícito — registrar o alvo com `DynamicScalingInSuspended` e `DynamicScalingOutSuspended` em `true` e retomar depois. Não faça isso durante uma campanha.
As decisões, e o que se perde em cada uma
📋 Uma API .NET 8 em ECS Fargate que atende 25 requisições por segundo em regime e 260 durante a campanha de aniversário das lojas, com rampa de 90 s, data conhecida uma semana antes, SLO de p99 abaixo de 800 ms e sem plantão noturno.
A decisão não é entre as três: é reconhecer que elas respondem a perguntas diferentes. Target tracking responde "quanta capacidade a chegada atual pede", e a métrica de chegada é a única antecedente e proporcional por construção. A ação programada responde "o que fazer com o pico cuja data eu sei", e a resposta é não esperar métrica nenhuma — nenhuma política reativa vence um atraso de 200 s contra uma rampa de 90 s. O step scaling responde "e quando a subida é um degrau", e aí um salto fixo chega antes que um cálculo proporcional. Escolher só uma das três deixa descoberto o caso das outras duas, e é isso que produz a discussão sobre "qual número subir".
Alt: Target tracking em CPU (o padrão de todo tutorial) — Falha o teste de proporcionalidade nesta aplicação: a task espera o banco, a CPU média fica em torno de 35% com 4 ou com 13 tasks, e o alvo de 70% nunca é atingido. Continua sendo a escolha certa para serviço genuinamente limitado por CPU — o defeito não é a métrica, é usá-la sem o teste.
Alt: Só step scaling, com degraus escritos à mão — Dá controle fino e obriga a reconstruir à mão a proteção contra oscilação que o target tracking traz embutida. A própria AWS recomenda target tracking quando a métrica é proporcional à capacidade, e reserva step scaling para resposta mais agressiva num nível específico.
Alt: Só ação programada, com capacidade fixa por faixa de horário — É a peça mais confiável e a mais cega: ela não sabe que hoje a campanha converteu o dobro. Sem uma política reativa por cima, o pico que excede a previsão não tem resposta, e o pico que não veio é capacidade paga à toa.
Alt: Escala preditiva (predictive scaling) — Resolve exatamente o problema do atraso, prevendo com base em padrão histórico, e exige no mínimo 24 h de histórico com duas semanas como ideal. Campanha de aniversário acontece uma vez por ano: não é padrão diário nem semanal, e portanto não é o caso dela. Para o ciclo comercial de todo dia, é.
Alt: Superprovisionar fixo em 14 tasks — Funciona, é a resposta mais rápida a implementar, e paga 24 h por dia por capacidade usada 20 min por ano. Legítimo em serviço cuja indisponibilidade custa mais que a diferença — e aí a decisão é financeira, não arquitetural.
Alt: Fila entre a borda e o processamento (SQS) — Troca o problema de escalar rápido pelo de aceitar resposta assíncrona: a chegada vira profundidade de fila e a capacidade deixa de precisar chegar em 90 s. É a resposta certa para o que pode esperar, e é o L22 — mas consulta de catálogo com cliente na frente não pode.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Métrica de regime | `ALBRequestCountPerTarget` a 1.200/min por alvo | CPU; memória; concorrência publicada pela aplicação; profundidade de fila | é antecedente e proporcional por construção, porque o ALB divide pelos alvos saudáveis | não vê requisição que já está esperando dentro da task; para isso, concorrência é melhor |
| Métrica de apoio | CPU em alta resolução, como piso | só a de chegada; nenhuma segunda política | cobre o caso em que a carga por requisição muda, como regressão de índice | uma segunda política significa que a descida só ocorre quando ambas concordam |
| Tipo de política de regime | target tracking | step scaling com degraus escritos à mão | a métrica é proporcional, e a proteção contra oscilação vem embutida | menos controle fino, e o número de períodos do alarme gerenciado não é visível |
| Resposta ao degrau | step scaling em alarme próprio, +50% e +100% | só target tracking; alvo mais baixo para reagir antes | um salto fixo chega antes que um cálculo proporcional quando a chegada dobra | passa a existir um limiar que você mantém à mão, e ele envelhece com o produto |
| Pico com data | ação programada elevando o piso | confiar na política reativa; superprovisionar o mês | não observa métrica, não tem atraso, e custa apenas a janela | é cega: não sabe que hoje a campanha converteu o dobro |
| Cooldown de subida | 60 s | 300 s (padrão para ECS); 0 | com rampa de 90 s, o padrão permite uma leva e cinco minutos de espera | levas mais frequentes podem passar do necessário quando a rampa é curta |
| Cooldown de descida | 300 s | igual ao de subida; 900 s | descer é o movimento perigoso, e o target tracking já é conservador por conta própria | capacidade paga por alguns minutos depois de o pico passar |
| Piso | 4 tasks em duas AZs | 2 tasks (o mínimo do regime); 1 | uma falha de AZ não pode cortar a capacidade do pico pela metade | duas tasks pagas em regime que o tráfego não exige |
| Teto | 40 tasks, derivado do banco | 100; ilimitado; o que o orçamento aguentar | 40 × pool de 10 = 400 conexões, dentro do teto lido do grupo de parâmetros | a partir de 40, a resposta deixa de ser escalar tasks e passa a ser o L07 e o L13 |
| Escala preditiva | não, e o motivo é o padrão | ligar em modo de previsão apenas; ligar para escalar | campanha anual não é padrão diário nem semanal; ela precisa de 24 h de histórico e de ciclo | para o ciclo comercial de todo dia ela seria melhor que a ação programada — fica no roteiro |
A dívida que a escala automática cria, e que este módulo não paga
Escalar a camada de tasks transfere a pressão para a camada de baixo. Quarenta tasks abrem até quatrocentas conexões no Postgres, e um banco que atendia bem quatro tasks pode ficar com a CPU em 100% servindo quarenta. Este módulo faz a única coisa honesta que cabe nele: calcula o teto e o declara como `max_capacity`, para que a queda não mude de lugar em silêncio. Resolver de verdade é réplica de leitura e pool no L07 e cache no L13. Enquanto você não tiver isso, o teto é a proteção.
Construir: o teste de carga que produz o alvo
A ordem importa e é contraintuitiva: o teste de carga vem ANTES da política, não depois. Sem ele você não tem o teto por task, e sem o teto o valor do alvo é chute. Duas medições, e a segunda é a que quase ninguém faz.
// carga.js — o teste que produz o alvo, e depois o grafico do evento
//
// Roda com: k6 run -e URL=https://api.exemplo.com carga.js
// Nao e um teste de estresse ate a quebra: aquilo e o L40. Aqui o objetivo e
// outro e mais estreito — descobrir DUAS coisas com numero:
// 1. quantas requisicoes por segundo UMA task sustenta dentro do SLO;
// 2. se a metrica candidata e proporcional a capacidade.
import http from 'k6/http';
import { check } from 'k6';
import { Trend } from 'k6/metrics';
const latencia = new Trend('latencia_ms', true);
export const options = {
scenarios: {
// ── Fase 1: escada, com a frota TRAVADA em 1 task ─────────────────────────
// Travar a frota e a parte que quase todo mundo esquece. Com escala ligada,
// o teste mede o conjunto e nao a peca, e o teto por task sai errado.
// Suspenda a escala antes:
// aws application-autoscaling register-scalable-target ... \
// --suspended-state DynamicScalingInSuspended=true,DynamicScalingOutSuspended=true
escada: {
executor: 'ramping-arrival-rate',
startRate: 5, timeUnit: '1s',
preAllocatedVUs: 200, maxVUs: 800,
stages: [
{ target: 10, duration: '3m' }, // cada patamar dura 3 min: 1 min para
{ target: 20, duration: '3m' }, // aquecer e 2 min de leitura estavel.
{ target: 30, duration: '3m' }, // Patamar curto le transiente, nao regime.
{ target: 40, duration: '3m' },
{ target: 50, duration: '3m' },
],
},
},
thresholds: {
// O SLO e o criterio de teto: a task nao "cai" em 40 req/s, ela sai do SLO.
'http_req_duration{expected_response:true}': ['p(99)<800'],
'http_req_failed': ['rate<0.001'],
},
};
export default function () {
const r = http.get(`${__ENV.URL}/api/catalogo/${__ENV.LOJA}`);
latencia.add(r.timings.duration);
check(r, { 'status 200': (x) => x.status === 200 });
}
// ── Fase 2: o teste de PROPORCIONALIDADE ─────────────────────────────────────
// Repita a fase 1 com desiredCount = 2, no MESMO patamar de pico da fase 1.
// A metrica candidata tem de cair pela metade. Resultado na aplicacao de exemplo:
//
// metrica 1 task @30 req/s 2 tasks @30 req/s proporcional?
// CPUUtilization 34% 33% NAO
// MemoryUtilization 61% 60% NAO
// RequestCountPerTarget 1.800/min 900/min SIM
//
// A CPU nao cai porque a task nao estava usando CPU: estava esperando o banco.
// A memoria nao cai porque o coletor de lixo do .NET mantem o heap alocado — a
// AWS recomenda explicitamente NAO escalar aplicacao .NET por memoria, por isso.
// A contagem por alvo cai pela metade por construcao: o ALB divide pelo numero
// de alvos saudaveis. Este quadro e o que decide a metrica; nao a preferencia.
// ── Fase 3: o EVENTO, com a escala religada ──────────────────────────────────
// Reproduza a rampa real da campanha — 25 para 260 req/s em 90 s — e deixe as
// politicas trabalharem. O grafico deste evento e o entregavel do laboratorio.
export const rampaDaCampanha = {
executor: 'ramping-arrival-rate',
startRate: 25, timeUnit: '1s',
preAllocatedVUs: 500, maxVUs: 3000,
stages: [
{ target: 25, duration: '3m' }, // regime, para a linha de base existir
{ target: 260, duration: '90s' }, // a rampa: e ela que testa os atrasos
{ target: 260, duration: '10m' }, // o platô: e aqui que se ve se estabilizou
{ target: 25, duration: '5m' }, // a descida: e aqui que se ve se oscila
],
};
A fase 2 é o teste de proporcionalidade, e é ele que decide a métrica. A AWS declara as duas propriedades exigidas: a métrica precisa mudar quando a demanda muda, e precisa cair pela metade quando você dobra a capacidade. Uma métrica que passa na primeira e falha na segunda produz escala automática que sobe até o máximo e não estabiliza — ou, como aqui, que nunca sobe.
| Métrica candidata | 1 task a 30 req/s | 2 tasks a 30 req/s | Proporcional? | Por que |
|---|---|---|---|---|
| `CPUUtilization` (AWS/ECS) | 34% | 33% | não | a task espera o banco; ela não estava usando CPU, então dividir o trabalho não reduz nada |
| `MemoryUtilization` (AWS/ECS) | 61% | 60% | não | o coletor de lixo do .NET mantém o heap alocado; a AWS recomenda explicitamente não escalar aplicação .NET por memória |
| `RequestCountPerTarget` (ALB) | 1.800/min | 900/min | sim | o ALB divide a contagem do grupo pelo número de alvos saudáveis — é aritmética, não comportamento |
| `api.requisicoes.em_voo` (própria) | 18 | 9 | sim | concorrência dividida pelas réplicas; é a melhor escolha para servidor limitado por trabalhador |
| `TargetResponseTime` p99 | 780 ms | 390 ms | sim, e não use | é proporcional e é sintoma: escalar por latência significa esperar a dor antes de agir |
Por que a latência é proporcional e continua sendo a métrica errada
Ela satisfaz as duas propriedades formais e reprova por um terceiro critério que não está na regra: ela é consequente. Quando o p99 subiu, o cliente já esperou. Métrica de chegada permite agir antes de existir dor; métrica de latência garante que a dor aconteça em cada evento de escala. Use latência para alarme e para SLO — que é o L52 — e chegada para escalar.
O erro de método que invalida o teste inteiro
Rodar o teste de carga com a escala automática ligada. Você mede o conjunto, não a peça: a frota cresce durante a medição e o teto por task sai inflado, tipicamente por um fator igual ao número de tasks que subiram. Suspenda a escala com `--suspended-state DynamicScalingInSuspended=true,DynamicScalingOutSuspended=true` antes da fase 1 e da fase 2, e religue para a fase 3. Esquecer de religar é o segundo erro, e ele só aparece na campanha.
Construir: o alvo escalável e a política de regime
O alvo escalável não é um recurso da ECS. É um registro no Application Auto Scaling dizendo qual dimensão de qual recurso pode ser mexida e entre que limites. As políticas se penduram nele, e é por isso que apagar o alvo apaga tudo.
# escala-alvo.tf — o alvo escalavel e o regime
# O "alvo escalavel" nao e um recurso da ECS: e um registro no Application Auto
# Scaling dizendo QUAL dimensao de QUAL recurso pode ser mexida, e entre que
# limites. Sem ele, nenhuma politica tem onde se prender.
resource "aws_appautoscaling_target" "api" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.principal.name}/${aws_ecs_service.api.name}"
scalable_dimension = "ecs:service:DesiredCount"
# PISO: 4 tasks, duas por AZ. Nao e o minimo para atender o regime (2 dariam);
# e o minimo para uma falha de AZ nao cortar a capacidade pela metade no pico.
min_capacity = 4
# TETO: derivado do banco, nao do orcamento. 40 tasks x pool de 10 = 400
# conexoes. Leia o max_connections EFETIVO do seu grupo de parametros antes de
# copiar este numero — no RDS ele e derivado da memoria da instancia.
# aws rds describe-db-parameters --db-parameter-group-name <grupo> \
# --query "Parameters[?ParameterName=='max_connections']"
max_capacity = 40
}
# ── O REGIME: target tracking na metrica de CHEGADA ───────────────────────────
resource "aws_appautoscaling_policy" "chegada" {
name = "${var.projeto}-api-chegada"
policy_type = "TargetTrackingScaling"
service_namespace = aws_appautoscaling_target.api.service_namespace
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
target_tracking_scaling_policy_configuration {
# 1.200 requisicoes por MINUTO por alvo. A unidade e por minuto: o teto
# medido de 30 req/s da 1.800 req/min, e 0,67 disso da 1.200. Ler este valor
# como "por segundo" erra o alvo por um fator de 60 — e a politica passa a
# escalar para 60x a capacidade necessaria.
target_value = 1200
predefined_metric_specification {
predefined_metric_type = "ALBRequestCountPerTarget"
# O rotulo do recurso e a concatenacao dos sufixos dos dois ARNs, separados
# por barra: app/<nome>/<id>/targetgroup/<nome>/<id>. `arn_suffix` devolve
# exatamente essas duas partes, e e por isso que ele existe no provider.
# Montar essa string a mao com o ARN completo e o erro numero um aqui: a
# politica e criada, o alarme nao encontra a metrica e nada escala.
resource_label = "${aws_lb.principal.arn_suffix}/${aws_lb_target_group.api.arn_suffix}"
}
# SUBIDA: 60 s em vez dos 300 s padrao para ECS. Com uma rampa de 90 s, o
# padrao permite UMA leva de capacidade e depois cinco minutos de espera.
scale_out_cooldown = 60
# DESCIDA: 300 s, e de proposito mais longo que a subida. O target tracking
# ja e conservador na descida por conta propria — ele so remove capacidade
# quando a metrica cai bem abaixo do alvo, tipicamente mais de 10% abaixo,
# justamente para nao oscilar. O cooldown longo reforca isso.
scale_in_cooldown = 300
# Deixado explicitamente em false: queremos que ele desca. Ligar
# `disable_scale_in` faz sentido quando a descida vai ser feita por OUTRA
# politica (step scaling na descida), e nao para "evitar risco" — nesse caso
# o que se ganha em seguranca se paga em fatura todos os dias.
disable_scale_in = false
}
}
# ── PISO DE SEGURANCA: utilizacao em alta resolucao ───────────────────────────
# Segunda politica de target tracking, permitida porque usa OUTRA metrica. Ela
# existe para o caso em que a chegada nao explica a carga: uma consulta que
# ficou caro por regressao de indice sobe a CPU sem subir o numero de
# requisicoes. Com duas politicas, a ECS SOBE se qualquer uma pedir e so DESCE
# se todas concordarem — a prioridade e disponibilidade, por projeto.
resource "aws_appautoscaling_policy" "utilizacao" {
name = "${var.projeto}-api-cpu-20s"
policy_type = "TargetTrackingScaling"
service_namespace = aws_appautoscaling_target.api.service_namespace
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
target_tracking_scaling_policy_configuration {
target_value = 70
predefined_metric_specification {
# A variante de alta resolucao avalia a metrica a cada 20 s em vez de 60.
# Ela EXIGE que o servico tenha a configuracao de monitoramento com
# resolutionSeconds = 20 aplicada e implantada ANTES — senao a politica e
# criada e nao encontra pontos de metrica no periodo que espera.
predefined_metric_type = "ECSServiceAverageCPUUtilizationHighResolution"
}
scale_out_cooldown = 60
scale_in_cooldown = 300
}
}
O `resource_label` é onde este laboratório mais falha em silêncio
Ele não é o ARN do balanceador nem o do grupo de destino: é a concatenação dos SUFIXOS dos dois, separada por barra, na forma `app/<nome>/<id>/targetgroup/<nome>/<id>`. O `arn_suffix` do provider devolve exatamente essas duas partes, e existe por isso. Quem monta a string com o ARN completo cria a política com sucesso, vê o alarme em estado de dado insuficiente e passa horas procurando erro na aplicação. Confira depois de aplicar: `aws application-autoscaling describe-scaling-policies` mostra o rótulo que ficou gravado.
| Parâmetro | Padrão para ECS | Aqui | Por quê |
|---|---|---|---|
| `target_value` | não tem padrão | 1.200 | por minuto e por alvo: 30 req/s × 60 × 0,67 |
| `scale_out_cooldown` | 300 s | 60 s | a rampa dura 90 s; o padrão permite uma leva e cinco minutos de espera |
| `scale_in_cooldown` | 300 s | 300 s | o padrão está correto: descer é o movimento perigoso |
| `disable_scale_in` | false | false (explícito) | ligar só faz sentido quando outra política cuida da descida, não para "evitar risco" |
| `min_capacity` | não tem padrão | 4 | duas AZs; uma falha de AZ não pode cortar o pico pela metade |
| `max_capacity` | não tem padrão | 40 | derivado do `max_connections` lido, não do orçamento |
| Métrica | nenhuma | `ALBRequestCountPerTarget` | a única predefinida da ECS que mede chegada; as outras quatro medem utilização |
Construir: o degrau súbito e a campanha com data
As duas peças que faltam respondem a perguntas que o regime não responde. O step scaling responde ao degrau, e a diferença de responsabilidade em relação ao target tracking é total: aqui o alarme é seu, com limiar, estatística e número de períodos que você escreve e mantém.
# escala-degrau.tf — o degrau subito e a campanha com data
# O alarme e SEU: no step scaling voce escreve o limiar, a estatistica e o numero
# de periodos. E o oposto do target tracking, onde a politica cria e administra os
# alarmes e a documentacao pede explicitamente para voce NAO os editar.
resource "aws_cloudwatch_metric_alarm" "chegada_dobrou" {
alarm_name = "${var.projeto}-api-chegada-dobrou"
namespace = "AWS/ApplicationELB"
metric_name = "RequestCountPerTarget"
# A unica estatistica valida para esta metrica e Sum — e, apesar do nome, o
# valor JA e a media por alvo: o ALB divide a contagem do grupo pelo numero de
# alvos saudaveis. Pedir Average aqui devolve um numero sem significado, e e a
# pegadinha mais comum desta metrica.
statistic = "Sum"
period = 60
evaluation_periods = 1 # um periodo: sensibilidade comprada com risco de ruido
# 2.400 = o dobro do alvo do regime. Abaixo disso, o target tracking resolve
# com proporcionalidade e sem degrau. A margem em relacao ao alvo e o que evita
# as duas politicas brigarem pela mesma variavel.
threshold = 2400
comparison_operator = "GreaterThanThreshold"
# A metrica e sempre publicada quando ha alvos registrados, mas ausencia de
# trafego nao e violacao — e a madrugada nao pode acordar ninguem.
treat_missing_data = "notBreaching"
dimensions = {
LoadBalancer = aws_lb.principal.arn_suffix
TargetGroup = aws_lb_target_group.api.arn_suffix
}
# O alarme dispara a POLITICA, nao um topico SNS. Este e o ponto que confunde:
# a acao de alarme aqui e o ARN da politica de escala.
alarm_actions = [aws_appautoscaling_policy.degrau.arn]
}
resource "aws_appautoscaling_policy" "degrau" {
name = "${var.projeto}-api-degrau"
policy_type = "StepScaling"
service_namespace = aws_appautoscaling_target.api.service_namespace
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
step_scaling_policy_configuration {
# Percentual, nao numero absoluto: +50% em 4 tasks sao 2, e em 20 tasks sao
# 10. Um degrau de "+6 tasks" seria exagero no regime e insuficiente no pico.
adjustment_type = "PercentChangeInCapacity"
# Com PercentChangeInCapacity, 50% de 4 tasks arredonda para 2. O minimo
# explicito impede que a frota pequena receba um degrau irrelevante.
min_adjustment_magnitude = 2
# A agregacao e aplicada aos pontos mais recentes ANTES de comparar com os
# limites dos degraus. Average e o padrao de fato desta escolha; Maximum
# tornaria o degrau mais agressivo e mais suscetivel a um pico isolado.
metric_aggregation_type = "Average"
# Curto de proposito: o degrau existe para responder rapido. O cooldown do
# step scaling tambem conta a capacidade recem-adicionada como parte da
# capacidade desejada do proximo degrau, entao ele nao empilha cegamente.
cooldown = 60
# Os limites sao RELATIVOS ao limiar do alarme (2.400), nao absolutos. De
# 0 a 1.200 acima (ou seja, de 2.400 a 3.600): +50%. Acima de 1.200 acima
# (mais de 3.600 por alvo): +100%. Os intervalos nao podem se sobrepor nem
# deixar lacuna, e exatamente um deles pode ter limite superior nulo.
step_adjustment {
metric_interval_lower_bound = 0
metric_interval_upper_bound = 1200
scaling_adjustment = 50
}
step_adjustment {
metric_interval_lower_bound = 1200
scaling_adjustment = 100
}
}
}
# ── A CAMPANHA: capacidade planejada, sem metrica no caminho ──────────────────
# A acao programada nao observa nada, e e por isso que ela nao tem atraso. Ela
# mexe nos LIMITES do alvo escalavel, nao no desiredCount: elevar o MinCapacity
# obriga o servico a subir, e as politicas reativas continuam trabalhando por
# cima desse piso novo. Acao programada tambem dispara durante um cooldown, sem
# esperar ele terminar.
resource "aws_appautoscaling_scheduled_action" "campanha_sobe" {
name = "${var.projeto}-api-campanha-sobe"
service_namespace = aws_appautoscaling_target.api.service_namespace
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
# 15 min antes do disparo do e-mail, com folga para os quatro atrasos e para o
# health check. O fuso e explicito: sem `timezone`, o cron e interpretado em
# UTC, e uma campanha marcada para 19h no Brasil sairia as 16h.
schedule = "cron(45 18 5 9 ? 2026)"
timezone = "America/Sao_Paulo"
scalable_target_action {
# 14 tasks: as 13 que o teste de carga mostrou necessarias para 260 req/s,
# mais uma de folga. Nao e chute — sai da divisao da chegada prevista pelo
# alvo por alvo.
min_capacity = 14
max_capacity = 40
}
}
resource "aws_appautoscaling_scheduled_action" "campanha_desce" {
name = "${var.projeto}-api-campanha-desce"
service_namespace = aws_appautoscaling_target.api.service_namespace
resource_id = aws_appautoscaling_target.api.resource_id
scalable_dimension = aws_appautoscaling_target.api.scalable_dimension
# Duas horas depois. Devolver o piso NAO derruba tasks: ele apenas deixa de
# obrigar. Quem remove capacidade e a politica reativa, quando a chegada cair —
# e ela e conservadora nisso de proposito. Sem esta segunda acao, o piso de 14
# tasks continua valendo para sempre, e e o erro de esquecimento mais caro
# deste laboratorio.
schedule = "cron(0 21 5 9 ? 2026)"
timezone = "America/Sao_Paulo"
scalable_target_action {
min_capacity = 4
max_capacity = 40
}
}
A pegadinha da estatística de `RequestCountPerTarget`
A única estatística válida para essa métrica é `Sum` — e, apesar do nome, o valor já é a média por alvo: o ALB divide a contagem do grupo pelo número de alvos saudáveis antes de publicar. Pedir `Average` devolve um número sem significado físico, e o alarme criado sobre ele dispara em momentos que ninguém consegue explicar. A documentação diz a frase inteira: "the only valid statistic is Sum. This represents the average not the sum".
| Aspecto | Target tracking | Step scaling |
|---|---|---|
| Quem cria o alarme | a própria política, e a documentação pede para não editá-lo | você, com limiar e períodos explícitos |
| Forma da resposta | proporcional à distância do alvo | degrau fixo por faixa de violação |
| Proteção contra oscilação | embutida: só desce quando cai bem abaixo do alvo | sua responsabilidade, pela margem entre os limiares de subida e descida |
| Quantos períodos até agir | não documentado; meça no seu evento | você define em `evaluation_periods` |
| Quando é a escolha certa | métrica proporcional e necessidade de regime estável | resposta mais agressiva num nível específico, ou degrau grande e súbito |
| Risco de misturar os dois | — | se o step desce antes de o target tracking estar pronto para descer, a descida não é bloqueada |
Por que a ação programada é a peça mais confiável do desenho
Ela não observa métrica nenhuma, então não tem nenhum dos quatro atrasos. Ela também não mexe no desejado: mexe nos LIMITES do alvo escalável, elevando o `MinCapacity`, o que obriga o serviço a subir e deixa as políticas reativas trabalhando por cima do piso novo. E ela dispara no horário marcado mesmo que um cooldown esteja em curso, sem esperar ele terminar. Para pico com data conhecida, nenhuma política reativa compete com isso — e é por isso que a resposta certa para a campanha não é uma política melhor.
Construir: a aplicação que não mente sobre a própria carga
Duas coisas mudam no código, e as duas existem porque a escala automática as tornou parâmetros de capacidade em vez de detalhes de implementação. O tamanho do pool de conexões passou a ser multiplicado pelo número de tasks, e a concorrência em voo passou a ser a informação que nenhuma métrica gerenciada tem.
// Program.cs — a aplicacao que nao mente sobre a propria carga
var builder = WebApplication.CreateBuilder(args);
// ── O TETO DE CONEXOES, EXPLICITO ─────────────────────────────────────────────
// O padrao do Npgsql para MaxPoolSize e generoso o suficiente para que 40 tasks
// esgotem um Postgres pequeno. Como a escala automatica multiplica o numero de
// tasks, o tamanho do pool deixa de ser detalhe de aplicacao e passa a ser um
// parametro de capacidade: max_capacity x MaxPoolSize <= max_connections do banco,
// com folga para manutencao e para a conexao de administracao.
var conexao = new NpgsqlConnectionStringBuilder(
builder.Configuration.GetConnectionString("Padrao"))
{
MaxPoolSize = builder.Configuration.GetValue("DB_POOL_MAX", 10),
MinPoolSize = 2,
// Fila interna curta: e melhor a requisicao falhar rapido e o retry com
// backoff do L36 tratar, do que 200 requisicoes esperando pool em silencio
// e estourando o tempo do ALB. Fila invisivel e o que faz a CPU ficar plana
// enquanto a latencia sobe.
Timeout = 5,
CommandTimeout = 8,
}.ToString();
builder.Services.AddDbContextPool<AppDb>(o => o.UseNpgsql(conexao));
// ── A METRICA QUE A APLICACAO SABE E O ALB NAO ────────────────────────────────
// `ALBRequestCountPerTarget` mede a chegada, e e a metrica de regime. Ela nao
// enxerga uma coisa: requisicao que chegou e esta ESPERANDO dentro da task. Para
// um servidor limitado por concorrencia, a AWS recomenda a propria aplicacao
// publicar concorrencia como metrica personalizada, ao menos uma vez por minuto.
// Publicamos porque e o insumo do teste de carga da secao seguinte — e porque e
// o que permite decidir o teto por task com numero em vez de intuicao.
builder.Services.AddSingleton<MedidorDeConcorrencia>();
builder.Services.AddOpenTelemetry()
.WithMetrics(m => m
.AddMeter("Cadencia.Api")
.AddOtlpExporter()); // coletor ADOT; o caminho e o do L08
var app = builder.Build();
// Middleware que conta requisicoes em voo. Uma unica variavel, incrementada na
// entrada e decrementada na saida — barato o suficiente para o caminho quente.
app.Use(async (ctx, next) =>
{
var medidor = ctx.RequestServices.GetRequiredService<MedidorDeConcorrencia>();
medidor.Entrou();
try { await next(); }
finally { medidor.Saiu(); }
});
// As duas rotas de saude do L03 continuam valendo, e agora carregam um segundo
// papel: durante a escala, `/health/ready` e o que decide QUANDO a task nova
// entra na conta de alvos saudaveis — e portanto quando ela passa a dividir o
// `RequestCountPerTarget`. Prontidao que mente encurta o atraso na aparencia e
// nao na pratica: a task entra na divisao antes de poder atender, o valor por
// alvo cai, a politica para de escalar, e a latencia continua subindo.
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = r => r.Tags.Contains("ready") });
app.MapGet("/api/catalogo/{lojaId:guid}", async (Guid lojaId, AppDb db) =>
// Esta e a rota da campanha, e e ela que define o perfil da aplicacao: uma
// consulta, pouca CPU, muita espera. Por isso a CPU nao e metrica de escala
// aqui — nao por ser "ruim", mas por nao ser proporcional a capacidade nesta
// forma de trabalho.
Results.Ok(await db.Produtos.Where(x => x.LojaId == lojaId).Take(50).ToListAsync()));
app.Run();
// A classe do medidor: o `ObservableGauge` e lido no momento da coleta, o que
// evita publicar um ponto por requisicao e explodir a cardinalidade.
public sealed class MedidorDeConcorrencia
{
private static readonly Meter Meter = new("Cadencia.Api");
private int _emVoo;
public MedidorDeConcorrencia() =>
Meter.CreateObservableGauge("api.requisicoes.em_voo", () => Volatile.Read(ref _emVoo));
public void Entrou() => Interlocked.Increment(ref _emVoo);
public void Saiu() => Interlocked.Decrement(ref _emVoo);
}
Fila invisível é o que faz a CPU ficar plana
Se o pool está esgotado e a requisição espera por uma conexão em silêncio, a CPU não sobe, a memória não sobe, a chegada não sobe — e a latência sobe. É a assinatura exata do incidente da Cadência, e é o motivo de o `Timeout` do pool estar em 5 s no código acima em vez do padrão generoso. Falhar rápido transforma espera invisível em erro contável, e erro contável tem métrica. O tratamento do erro com recuo e jitter é o L36.
Sobre a métrica de concorrência: para um servidor limitado por trabalhador, a AWS recomenda que a própria aplicação publique a concorrência, ao menos uma vez por minuto, e que se escale pela média por réplica. Ela é publicada aqui porque é o insumo do teste de carga e porque é a métrica que este desenho usaria se a rota da campanha tivesse limite de trabalhadores em vez de ser uma consulta simples. Declarar isso é mais honesto que fingir que a chegada resolve todo caso.
Construir: os 20 segundos de métrica, e a ordem que não pode ser trocada
A ECS publica utilização em 60 s por padrão, e desde junho de 2026 aceita 20 s por configuração de monitoramento no serviço. É a única das quatro parcelas de atraso que se compra com configuração, e ela tem uma armadilha de ordem: a política de alta resolução espera pontos a cada 20 s, e criá-la antes de o serviço publicar nessa resolução produz o mesmo sintoma da métrica errada — escala configurada que não faz nada.
# alta-resolucao.sh — os 20 s de metrica, e a ordem que nao pode ser trocada
set -euo pipefail
PROJETO=ffv-lab; REGIAO=us-east-1
# ATENCAO A ORDEM. A politica de alta resolucao espera pontos a cada 20 s. Se
# voce a criar ANTES de o servico publicar nessa resolucao, ela e criada com
# sucesso e nao encontra dado — e o sintoma e "escala configurada que nao faz
# nada", identico ao sintoma da metrica errada.
# ── 1. Ligar a configuracao de monitoramento no servico ───────────────────────
# Isto cria uma REVISAO nova do servico e dispara uma implantacao: e um rollout,
# com os mesmos tres relogios do L03. Nao faca isto durante a campanha.
aws ecs update-service --region "$REGIAO" \
--cluster "$PROJETO" --service "${PROJETO}-api" \
--monitoring 'metricConfigurations=[{metricNames=[CPUUtilization,MemoryUtilization],resolutionSeconds=20}]'
aws ecs wait services-stable --cluster "$PROJETO" --services "${PROJETO}-api" --region "$REGIAO"
# ── 2. Confirmar que a configuracao esta ATIVA ────────────────────────────────
# A configuracao de monitoramento NAO volta na resposta de create-service nem de
# update-service. Ela aparece na revisao do servico, e e ali que se confere.
REV=$(aws ecs describe-services --cluster "$PROJETO" --services "${PROJETO}-api" \
--query 'services[0].deployments[0].serviceRevisionArn' --output text)
aws ecs describe-service-revisions --service-revision-arns "$REV" \
--query 'serviceRevisions[0].monitoring'
# Esperado: {"metricConfigurations":[{"metricNames":["CPUUtilization","MemoryUtilization"],
# "resolutionSeconds":20}]}
# ── 3. Confirmar que os PONTOS existem a cada 20 s ────────────────────────────
# Esta e a prova real: periodo de 20 s tem de devolver tres pontos por minuto.
# Se devolver um, a implantacao terminou mas as tasks antigas ainda publicavam em
# 60 s — espere o rollout completar em todas.
aws cloudwatch get-metric-statistics --namespace AWS/ECS --metric-name CPUUtilization \
--dimensions Name=ClusterName,Value="$PROJETO" Name=ServiceName,Value="${PROJETO}-api" \
--start-time "$(date -u -v-10M +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 20 --statistics Average \
--query 'length(Datapoints)'
# Esperado: proximo de 30 numa janela de 10 min. Se vier proximo de 10, a
# resolucao ainda e de 60 s e a politica de alta resolucao nao deve ser criada.
# ── 4. So AGORA a politica ────────────────────────────────────────────────────
terraform apply -target=aws_appautoscaling_policy.utilizacao -auto-approve
# NAO VERIFICADO NA DOCUMENTACAO: se o provider Terraform da sua versao ja expoe
# a configuracao de monitoramento como bloco em `aws_ecs_service`. Em ago/2026 o
# caminho que confirmamos e o da CLI acima; se o seu provider ja tiver o bloco,
# use-o e remova este script, porque configuracao fora do IaC volta a divergir.
O que a alta resolução cobra, e o que ela não resolve
A funcionalidade em si não tem custo próprio, mas métrica de alta resolução é uma dimensão de cobrança do CloudWatch distinta da resolução padrão, que é gratuita para essas métricas de serviço. Calcule no AWS Pricing Calculator, porque o valor varia por região. E entenda o limite: ela encurta o primeiro atraso da UTILIZAÇÃO. A chegada pelo ALB continua em 60 s, a partida da task não muda, e o health check não muda. Se você só liga a alta resolução, o ganho é uma fração do atraso total.
Restrições que a documentação declara, e que decidem se você pode usar
A configuração de monitoramento cria uma revisão nova do serviço e dispara uma implantação — é um rollout, com os relógios do L03, e não se faz durante campanha. A métrica de alta resolução é suportada para serviços com Application Load Balancer e Network Load Balancer, e não é suportada para serviços com controlador de implantação `CODE_DEPLOY` ou `EXTERNAL`. Isso importa para quem for ao L39: adotar blue/green com CodeDeploy tira a alta resolução da mesa, e essa troca precisa ser feita de propósito.
Implantar, e provar com o gráfico do evento
Cinco provas. A terceira é o entregável do laboratório, e as outras quatro existem para que ela signifique algo. Nenhuma aceita "parece que escalou" como resultado.
# provas.sh — cinco medicoes. Nenhuma conclusao sai de "parece que escalou"
PROJETO=ffv-lab; REGIAO=us-east-1
RES="service/${PROJETO}/${PROJETO}-api"
TGSUF=$(aws elbv2 describe-target-groups --names "${PROJETO}-api" \
--query 'TargetGroups[0].TargetGroupArn' --output text | sed 's|.*:||')
LBSUF=$(aws elbv2 describe-load-balancers --names "$PROJETO" \
--query 'LoadBalancers[0].LoadBalancerArn' --output text | sed 's|.*:loadbalancer/||')
# ── Prova 1: a linha do tempo da escala, com carimbo de hora ──────────────────
# E a prova central. Compare o `StartTime` da primeira atividade com o instante
# em que a rampa comecou: a diferenca E o atraso total, medido, e nao estimado.
aws application-autoscaling describe-scaling-activities \
--service-namespace ecs --resource-id "$RES" \
--query 'ScalingActivities[].{inicio:StartTime,fim:EndTime,causa:Cause,status:StatusCode}' \
--output table
# Esperado no desenho de producao: primeira atividade dentro de 120 s do inicio
# da rampa. Acima de 240 s, o atraso 1 ou o 2 ainda esta no padrao.
# Se a lista vier VAZIA depois de um pico inteiro, a politica nunca disparou —
# e o problema e a metrica, nao o alvo. Va para a prova 2.
# ── Prova 2: a metrica escolhida e proporcional a capacidade? ─────────────────
# Duas leituras da MESMA carga com contagens de task diferentes. Se o valor nao
# cai perto da metade, a metrica nao serve para escalar — e a AWS exige essa
# propriedade, nao e preferencia nossa.
for N in 1 2; do
aws application-autoscaling register-scalable-target --service-namespace ecs \
--resource-id "$RES" --scalable-dimension ecs:service:DesiredCount \
--suspended-state DynamicScalingInSuspended=true,DynamicScalingOutSuspended=true
aws ecs update-service --cluster "$PROJETO" --service "${PROJETO}-api" --desired-count $N
aws ecs wait services-stable --cluster "$PROJETO" --services "${PROJETO}-api"
echo "== $N task(s) =="; k6 run -e URL="https://$(terraform output -raw dominio)" carga.js
done
# Esperado: RequestCountPerTarget cai ~50%; CPUUtilization NAO cai. O quadro da
# fase 2 do carga.js e o resultado desta prova, e ele e o que justifica a metrica.
# ── Prova 3: o grafico do evento — o entregavel, como arquivo ────────────────
# `get-metric-widget-image` devolve PNG. Tres series no mesmo eixo: chegada por
# alvo (a demanda), alvos saudaveis (a capacidade que de fato serve) e p99 (a dor).
cat > widget.json <<'JSON'
{ "width": 1200, "height": 500, "start": "-PT45M", "end": "PT0H",
"title": "Campanha: chegada por alvo x capacidade saudavel x p99",
"yAxis": { "left": { "label": "req/min por alvo" }, "right": { "label": "alvos / ms" } },
"metrics": [
[ "AWS/ApplicationELB", "RequestCountPerTarget", "TargetGroup", "TGSUF", { "stat": "Sum", "period": 60 } ],
[ "AWS/ApplicationELB", "HealthyHostCount", "TargetGroup", "TGSUF", "LoadBalancer", "LBSUF",
{ "stat": "Minimum", "period": 60, "yAxis": "right" } ],
[ "AWS/ApplicationELB", "TargetResponseTime", "TargetGroup", "TGSUF", "LoadBalancer", "LBSUF",
{ "stat": "p99", "period": 60, "yAxis": "right" } ]
],
"annotations": { "horizontal": [ { "value": 1200, "label": "alvo da politica" } ] } }
JSON
sed -i '' "s/TGSUF/$TGSUF/g; s|LBSUF|$LBSUF|g" widget.json
aws cloudwatch get-metric-widget-image --metric-widget file://widget.json \
--output-format png --output text --query MetricWidgetImage | base64 --decode > evento.png
# Esperado: a chegada por alvo sobe, cruza a linha de 1.200, os alvos saudaveis
# sobem ATRAS dela, e o p99 tem um corcova que termina quando as curvas se
# reencontram. A AREA dessa corcova e a fatia de pico que o cliente engoliu, e e
# o numero que se leva para a reuniao. Usamos HealthyHostCount e nao
# RunningTaskCount de proposito: o segundo exige Container Insights, que cobra
# como metrica personalizada, e o primeiro ja diz o que interessa — quantos
# alvos de fato estavam servindo.
# ── Prova 4: o teto de conexoes nao foi estourado ────────────────────────────
# Escalar a camada de task sem esta prova troca 504 por conexao recusada, e o
# segundo erro e pior: ele atinge tambem quem nao estava no pico.
aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value="${PROJETO}-db" \
--start-time "$(date -u -v-45M +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 60 --statistics Maximum --query 'max_by(Datapoints,&Maximum).Maximum'
# Esperado: abaixo de 70% do max_connections efetivo do seu grupo de parametros.
# Compare com o valor real, nao com um numero decorado:
aws rds describe-db-parameters --db-parameter-group-name "${PROJETO}-pg" \
--query "Parameters[?ParameterName=='max_connections'].[ParameterValue]" --output text
# ── Prova 5: a acao programada elevou o piso, e devolveu depois ──────────────
# A que mais se esquece de conferir. Um piso que subiu e nao voltou e capacidade
# paga por mes inteiro por causa de uma campanha de vinte minutos.
aws application-autoscaling describe-scalable-targets --service-namespace ecs \
--resource-ids "$RES" \
--query 'ScalableTargets[].{min:MinCapacity,max:MaxCapacity}' --output table
aws application-autoscaling describe-scheduled-actions --service-namespace ecs \
--resource-id "$RES" \
--query 'ScheduledActions[].{nome:ScheduledActionName,quando:Schedule,fuso:Timezone,acao:ScalableTargetAction}' \
--output table
# Esperado: min de volta em 4 depois da janela, e DUAS acoes programadas — a que
# sobe e a que desce. Uma so e o defeito.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · A linha do tempo da escala | `describe-scaling-activities` | primeira atividade dentro de 120 s do início da rampa | lista vazia significa que a política nunca disparou — o defeito é a métrica, não o alvo |
| 2 · Proporcionalidade da métrica | mesma carga com 1 e com 2 tasks | a métrica cai perto de 50% | se não cai, a métrica não serve; escalar por ela produz política que não estabiliza |
| 3 · O gráfico do evento | `get-metric-widget-image` em PNG | as três curvas com a corcova de p99 fechando quando a capacidade alcança a demanda | corcova que não fecha significa que o teto foi atingido, e o gargalo mudou de camada (L40) |
| 4 · O teto de conexões | `DatabaseConnections` máximo no evento | abaixo de 70% do `max_connections` lido do grupo de parâmetros | acima disso, `max_capacity` está errado e o próximo pico troca 504 por conexão recusada |
| 5 · O piso voltou | `describe-scalable-targets` depois da janela | `min` de volta em 4, e duas ações programadas registradas | uma ação só significa que o piso da campanha ficou valendo — custo silencioso, sem alarme |
Como se lê o gráfico do evento, que é a nota deste laboratório
Três séries no mesmo eixo de tempo: chegada por alvo, alvos saudáveis e p99 de resposta. Procure o instante em que a chegada cruza a linha de 1.200 e o instante em que os alvos saudáveis começam a subir — a distância entre os dois é o atraso, medido, e é o número que substitui a discussão sobre "qual valor colocar". Depois olhe a área em que a chegada esteve acima do que a capacidade daquele momento sustentava: essa área é a fatia de pico que o cliente engoliu. Se ela é aceitável, o desenho está pronto; se não, a resposta é escalar por agenda, não baixar o alvo.
Um serviço ECS com uma API .NET que consulta o RDS tem target tracking em `ECSServiceAverageCPUUtilization` com alvo de 70%. Durante o pico, o p99 vai a 9 s, a CPU média fica em 34% e nenhuma atividade de escala é registrada. Qual é a correção?
Quebrar de propósito: três falhas e o diagnóstico
As três produzem o mesmo relato — "a escala automática não funcionou" — e o que as separa é onde se olha primeiro. Provoque cada uma no ambiente de teste, com carga, e guarde o gráfico das três: elas são visualmente distintas, e reconhecer a forma economiza a próxima madrugada.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| A métrica que nunca chega ao alvo | volte a política para `ECSServiceAverageCPUUtilization` a 70% e rode a rampa | latência sobe, nenhuma atividade de escala acontece, e o painel de CPU parece saudável | `describe-scaling-activities` vazio e a série de CPU nunca cruzando o alvo | trocar a métrica, não baixar o alvo; e aplicar o teste de proporcionalidade antes |
| O rótulo do recurso montado errado | troque `arn_suffix` pelo ARN completo no `resource_label` | a política existe, o alarme fica em dado insuficiente e nada escala nunca | `describe-scaling-policies` mostra o rótulo gravado; `describe-alarms` mostra `INSUFFICIENT_DATA` | o rótulo é a concatenação dos dois sufixos: `app/.../targetgroup/...` |
| A serra: dois controladores na mesma variável | ponha o `scale_in_cooldown` em 0 e adicione um step scaling de descida com limiar colado no alvo do regime | capacidade em dente de serra durante o platô, com o custo subindo e a latência oscilando | a série de alvos saudáveis contra a de chegada: elas ficam em contrafase | margem entre os limiares de subida e descida, cooldown de descida maior que o de subida, e uma única política responsável pela descida |
A falha que nenhuma política pega
Escalar tasks quando o gargalo está no banco. A política vê a chegada subir, adiciona capacidade corretamente, e cada task nova abre conexões e piora a fila do Postgres — a latência sobe COM a escala, e a política, vendo a chegada por alvo ainda alta, sobe mais. É o único cenário deste módulo em que a escala automática funcionando conforme o projeto agrava o incidente. O `max_capacity` derivado do teto de conexões é a proteção que este módulo oferece; nomear o gargalo é o L40, e mover o teto é o L07.
Segurança: o que muda quando a capacidade deixa de ser fixa
Capacidade elástica muda o perfil de risco de duas formas. A primeira é que quem controla a política controla a fatura, então a permissão de configurar escala é permissão financeira. A segunda é mais sutil: um serviço que escala sozinho absorve ataque em vez de resistir a ele, e o custo do ataque passa a ser seu.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Tráfego malicioso vira fatura, porque a escala o absorve | média | alto | `max_capacity` derivado e bloqueio na borda antes da computação | chegada por alvo alta com taxa de conversão em zero | regra de limite de taxa no WAF; o bloqueio na borda é o L47 |
| Permissão de escala concedida à task role da aplicação | alta | médio | quem escala é a função vinculada ao serviço, não a sua aplicação | IAM Access Analyzer sobre uso real: a permissão nunca é exercida | remover; a linha não fazia nada além de ampliar superfície |
| `max_capacity` alto demais derruba o banco no próximo pico | média | alto | máximo derivado do `max_connections` lido, com pool fixado por task | alarme em `DatabaseConnections` acima de 70% do teto | baixar o máximo imediatamente; depois L07 para mover o teto de verdade |
| Alarme do target tracking editado à mão | média | médio | a documentação pede para não editar; restringir `PutMetricAlarm` por prefixo próprio | CloudTrail em `PutMetricAlarm` sobre alarme com prefixo de target tracking | apagar a política e recriar; o alarme editado volta ao estado correto |
| Ação programada esquecida elevando o piso | alta | médio | a ação que desce entra no mesmo commit da que sobe, sempre | orçamento por tag com alerta, e revisão de `describe-scheduled-actions` | devolver o piso; e adicionar o par ausente — o L09 é onde isso vira processo |
| Escala automática mascarando regressão de desempenho | média | médio | alarme de p99 independente da escala, e não só de disponibilidade | `TargetResponseTime` p99 subindo com chegada estável | investigar a versão publicada; o alarme de 5xx do alvo do L03 não pega latência |
| Credencial de configuração com `application-autoscaling:*` | média | médio | ações nomeadas com condição de namespace, não curinga | CloudTrail em `PutScalingPolicy` fora do pipeline | derivar a política do uso medido, que é o L41 |
// politica-de-quem-configura-escala.json
// Esta e a permissao de quem APLICA o Terraform — nao a de quem escala em tempo
// de execucao. Quem escala e a funcao vinculada ao servico do Application Auto
// Scaling (AWSServiceRoleForApplicationAutoScaling_ECSService), que a AWS cria e
// administra. Confundir as duas e o erro classico: pessoas acrescentam
// `application-autoscaling:*` a task role da aplicacao, onde ela nao faz nada.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ConfigurarEscala",
"Effect": "Allow",
"Action": [
"application-autoscaling:RegisterScalableTarget",
"application-autoscaling:DeregisterScalableTarget",
"application-autoscaling:PutScalingPolicy",
"application-autoscaling:DeleteScalingPolicy",
"application-autoscaling:PutScheduledAction",
"application-autoscaling:DeleteScheduledAction",
"application-autoscaling:Describe*"
],
// As acoes do Application Auto Scaling nao suportam permissao em nivel de
// recurso: nao existe ARN de alvo escalavel para colocar aqui. O escopo
// real vem da condicao abaixo, que limita ao namespace da ECS, e das
// permissoes de ECS deste mesmo documento. Sem esta frase o `*` seria
// preguica; com ela, e limite do servico.
"Resource": "*",
"Condition": {
"StringEquals": { "application-autoscaling:service-namespace": "ecs" }
}
},
{
"Sid": "AlarmesQueEuMesmoEscrevo",
"Effect": "Allow",
// Apenas o alarme do degrau: os alarmes do target tracking sao criados e
// apagados pela propria politica, e a documentacao pede explicitamente
// para nao edita-los. Restringimos por ARN de alarme, com prefixo do
// projeto — o que o `Describe` nao aceita, porque ele nao suporta recurso.
"Action": ["cloudwatch:PutMetricAlarm", "cloudwatch:DeleteAlarms"],
"Resource": "arn:aws:cloudwatch:us-east-1:111122223333:alarm:ffv-lab-api-*"
},
{
"Sid": "LerAlarmeNaoAceitaRecurso",
"Effect": "Allow",
// `DescribeAlarms` e operacao de listagem e nao suporta recurso especifico.
// E a segunda ocorrencia legitima de `*` neste documento, e tambem vem com
// a frase que a justifica.
"Action": ["cloudwatch:DescribeAlarms", "cloudwatch:GetMetricData"],
"Resource": "*"
},
{
"Sid": "MexerNoServicoAlvo",
"Effect": "Allow",
"Action": ["ecs:UpdateService", "ecs:DescribeServices"],
// Aqui o recurso EXISTE e e estreito: um servico, num cluster. Escrever
// `*` neste bloco autorizaria mexer em qualquer servico da conta.
"Resource": "arn:aws:ecs:us-east-1:111122223333:service/ffv-lab/ffv-lab-api"
},
{
"Sid": "CriarAFuncaoVinculadaUmaVez",
"Effect": "Allow",
"Action": "iam:CreateServiceLinkedRole",
"Resource": "*",
// A condicao e o que impede esta linha de ser um caminho para criar
// funcao vinculada de QUALQUER servico. Sem ela, `CreateServiceLinkedRole`
// e uma escalada de privilegio de aparencia inocente.
"Condition": {
"StringEquals": {
"iam:AWSServiceName": "ecs.application-autoscaling.amazonaws.com"
}
}
}
]
}
Os três `*` deste documento, e por que cada um se justifica
As ações do Application Auto Scaling não suportam permissão em nível de recurso: não existe ARN de alvo escalável para escrever. O escopo real vem da condição de `service-namespace`, e sem ela o bloco autorizaria mexer em escala de DynamoDB, Aurora e SageMaker na mesma conta. `cloudwatch:DescribeAlarms` é operação de listagem e também não aceita recurso. E `iam:CreateServiceLinkedRole` precisa de `*` porque o recurso é a função que ainda não existe — a condição `iam:AWSServiceName` é o que impede a linha de virar caminho para criar função vinculada de qualquer serviço. A regra não é evitar `*`: é que todo `*` venha com a frase que explica por que não pode ser mais estreito.
Observabilidade: as perguntas que o painel tem de responder
Um painel de escala responde a uma pergunta estreita durante o evento: a capacidade está alcançando a demanda, e a que distância. Métrica que não ajuda nessa comparação pertence a outro painel — e um painel com dezoito séries durante um pico é um painel que ninguém lê no momento em que precisaria.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Quanta demanda está chegando por alvo? | `RequestCountPerTarget`, `Sum`, 60 s | é a demanda dividida pela capacidade saudável: o próprio valor já é a comparação | linha de anotação em 1.200, que é o alvo |
| Quantos alvos estão de fato servindo? | `HealthyHostCount`, `Minimum` | a capacidade real; usar o desejado do serviço aqui mente durante a partida | nunca abaixo do piso de 4 |
| A política decidiu, e quando? | `describe-scaling-activities` | o carimbo de hora que, contra o início da rampa, dá o atraso medido | primeira atividade em até 120 s |
| O cliente sentiu? | `TargetResponseTime` p99 | a corcova entre a chegada da demanda e a chegada da capacidade | p99 acima de 800 ms por 2 períodos |
| Chegamos ao teto? | desejado do serviço igual a `max_capacity` | a política pediu mais do que ela pode: o gargalo saiu da camada de tasks | qualquer ocorrência, e é evento para investigar depois |
| A camada de baixo aguentou? | `DatabaseConnections`, `Maximum` | a pressão que a escala transferiu para o banco | 70% do `max_connections` lido |
| Estamos oscilando? | atividades de escala por hora | subidas e descidas alternadas indicam margem insuficiente entre limiares | mais de 6 mudanças de direção por hora |
| A alta resolução está ativa? | pontos de `CPUUtilization` com período de 20 s | se voltou a 60 s, uma implantação removeu a configuração de monitoramento | 3 pontos por minuto |
A métrica que engana durante o evento
O `desiredCount` do serviço sobe no instante da decisão da política, e um painel que o usa como série de capacidade mostra a capacidade chegando antes de ela existir — esconde exatamente os dois atrasos que este módulo ensina a ver. Use `HealthyHostCount` do grupo de destino: ele só conta o alvo que passou no health check e está recebendo tráfego. A diferença entre as duas curvas, durante a rampa, é a partida da task mais o health check, desenhada.
Escala: 10, 10 mil, 1 milhão
| Volume | O que acontece com a política | O que passa a doer | O que fazer |
|---|---|---|---|
| 10 req/s, 4 tasks | a política nunca age; o piso resolve tudo | nada, e é onde a escala automática é puro custo de complexidade | manter, porque o piso protege a AZ; a política fica para a campanha |
| 260 req/s, 13 tasks | é o cenário do laboratório: o regime alcança em cerca de 2 min | a fatia de pico comida durante o atraso | ação programada quando a data é conhecida; é a resposta deste módulo |
| 5 mil req/s, 250 tasks | o máximo de 40 é atingido e a política para de poder ajudar | endereços na sub-rede, cota de tasks da conta e conexões no banco | sub-rede dimensionada, cota conferida em Service Quotas, e o gargalo nomeado no L40 |
| Chegada por alvo em zero por horas | a política pode descer até o piso; até zero se o piso for 0 | partida a frio no primeiro cliente da manhã | `RequestCountPerTarget` publica zeros reais, então piso 0 funciona — bom para homologação, ruim para produção com cliente |
| Falha de AZ durante o pico | metade dos alvos some e a chegada por alvo dobra | a política reage à divisão, não à falha: ela vê demanda dobrada e escala | é o comportamento desejado; o piso de 4 garante que sobrem 2. Ensaiar isso é o L57 |
| Implantação durante o pico | a descida é desligada e a subida continua | o denominador da chegada por alvo muda com o rollout, sem a demanda mudar | não publicar durante campanha; se for inevitável, suspender a escala de propósito |
| Degrau de 20× em 10 s | nem o step scaling chega em tempo | a arquitetura síncrona é o limite, não a política | fila entre a borda e o processamento, que é o L22 — escala não resolve tudo |
O limite honesto da escala automática, em uma frase
Ela é boa para carga que sobe em minutos e ruim para carga que sobe em segundos, porque os quatro atrasos somam mais de um minuto no melhor caso. Quando a subida é mais rápida que isso, existem exatamente três respostas: prever (agenda ou escala preditiva), absorver (fila, e o cliente aceita resposta assíncrona) ou já estar grande (superprovisionar e pagar). Escolher uma política mais esperta não é uma delas.
Custo: o que este laboratório acrescenta e o que ele devolve
Este é o primeiro laboratório da banda que pode DIMINUIR a fatura, e vale entender por quê: o desenho anterior mantinha capacidade de pico o tempo todo ou não mantinha nenhuma. O que a escala automática cobra é atenção; o que ela devolve é a diferença entre pagar pelo pico e pagar pela carga.
| Cenário | Volume | O que muda na fatura | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | homologação, sem cliente | piso de 1 e teto de 2; com `RequestCountPerTarget` e piso 0, chega a zero fora de uso | cai muito | piso 0 e aceitar a partida a frio — o ambiente não tem cliente esperando |
| Produção pequena | 25 req/s em regime, uma campanha por ano | piso de 4 em vez de capacidade de pico fixa; 20 min por ano de 14 tasks | cai, e a economia é a diferença entre 14 e 4 tasks nas outras 8.759 horas | métrica de 20 s só se o atraso incomodar; a de 60 s é gratuita nessas métricas |
| Alta escala | picos diários, frota entre 8 e 40 tasks | a elasticidade passa a ser a maior alavanca de custo da arquitetura | depende do perfil de carga, e agora é mensurável | compromisso de capacidade só para o PISO, nunca para o pico — é o raciocínio do L59 |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Fargate | vCPU-segundo e GB-segundo por task de pé | é proporcional à área embaixo da curva de capacidade, não ao pico — e é por isso que o gráfico do evento é também um documento de custo |
| Métrica de alta resolução do CloudWatch | dimensão de cobrança distinta da resolução padrão | a resolução de 60 s dessas métricas de serviço é gratuita; a de 20 s não é. Calcule antes de ligar |
| Alarmes | por alarme-mês | target tracking cria dois alarmes por política; duas políticas dão quatro, mais o seu do degrau |
| Container Insights | métricas personalizadas | não é necessário para escalar. `HealthyHostCount` do ALB responde a mesma pergunta de graça |
| LCU do ALB | conexões novas, ativas, bytes e avaliações de regra | o pico da campanha aparece aqui também, e ele não é evitável escalando tasks |
| Piso esquecido no alto | horas de task que ninguém pediu | é o maior custo oculto deste módulo, e não gera alarme nenhum porque nada está errado |
O ganho que não está na fatura da AWS
A campanha do ano passado converteu uma fração do previsto porque o carrinho não abriu nos primeiros minutos, que é justamente quando a intenção de compra é maior. O custo atacado aqui não é o de Fargate: é o da receita que não entrou. Isso muda o cálculo de "vale a pena ligar 14 tasks por 20 minutos" de uma decisão de infraestrutura para uma conta de negócio — e ela é fácil.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | política derivada de medição, com o gráfico do evento como registro do que aconteceu | o teto por task é de hoje; ele envelhece a cada mudança de consulta | repetir o teste de carga no pipeline e falhar quando o teto cair (L40, L54) | alta |
| Segurança | permissão de configurar escala separada da de execução, com os `*` justificados | a escala absorve tráfego malicioso e o transforma em fatura | limite de taxa e regra gerenciada na borda (L47) | média |
| Confiabilidade | piso que sobrevive a uma AZ, teto que protege o banco, descida conservadora | a hipótese de que uma AZ pode cair nunca foi exercitada | injeção de falha de AZ com hipótese escrita (L57) | alta |
| Eficiência de performance | métrica antecedente e proporcional, com os quatro atrasos medidos e nomeados | o gargalo real da arquitetura ainda não foi encontrado, só evitado pelo teto | curva de carga até a quebra, com o gargalo nomeado (L40) | alta |
| Otimização de custos | capacidade acompanha a carga; o pico deixou de ser pago 24 h por dia | piso elevado por ação programada esquecida não gera nenhum sinal | orçamento por tag com alerta (L09) e compromisso só sobre o piso (L59) | média |
| Sustentabilidade | nenhuma task de pé sem demanda que a justifique, o que é o próprio ganho energético | o piso de 4 é dimensionado por resiliência e fica ocioso na madrugada | avaliar Aurora Serverless v2 e piso menor fora do horário comercial (L15) | baixa |
Evolução em níveis: o que muda, e o que passa a doer
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de desenho. Cada nível resolve um risco e compra outro, e a segunda parte da nota é a que raramente se escreve.
Um `desired_count` no Terraform, mudado à mão quando dói. É onde a Cadência estava, e continua legítimo em homologação.Target tracking em CPU, com os padrões. É o que todo tutorial ensina, e funciona de verdade para serviço limitado por CPU.Chegada por alvo como regime, degrau para o súbito, ação programada para a campanha, teto derivado do banco e o gráfico do evento como prova.Curva de carga até a quebra com o gargalo identificado (L40), fila entre a borda e o que pode esperar (L22), réplica de leitura e cache movendo o teto (L07, L13).SLO com orçamento de erro decidindo quando escalar e quando degradar (L52), compromisso de capacidade sobre o piso (L59), experimento de falha de AZ ensaiado (L57).Escala preditiva sobre o padrão diário e semanal, em modo de previsão primeiro, com `SchedulingBufferTime` cobrindo a partida — e a política reativa por baixo, para o que a previsão não viu.A ordem não é negociável, e o motivo é concreto
A escala preditiva do nível 6 precisa de padrão histórico: no mínimo 24 h de dados, com duas semanas como ideal, e ela analisa até 14 dias para gerar previsão de 48 h. Quem liga previsão antes de ter métrica correta no nível 3 está prevendo a série errada — e uma previsão excelente de uma métrica que não é proporcional à capacidade produz capacidade errada com muita confiança. A previsão amplifica a qualidade da métrica, nos dois sentidos.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo há um caso raro em que a resposta é intermediária, e vale ser preciso sobre ela. A parte central do problema é determinística: escolher a métrica é um teste com duas medições, e derivar o alvo é uma divisão. Nenhum modelo melhora isso, e pedir a um modelo para "sugerir o alvo ideal" troca uma conta verificável por uma opinião plausível.
A parte do atraso, porém, é genuinamente um problema de previsão — e a AWS já a resolveu como produto, com escala preditiva. Ela analisa o histórico de carga para antecipar a demanda e provisiona antes. Isso é aprendizado sobre série temporal, não um modelo de linguagem, e é a forma correta de IA para este problema: gerenciada, avaliável em modo de previsão antes de agir, e com a política reativa por baixo como rede.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Que problema a IA resolve aqui? | o atraso: prever a demanda da próxima hora e provisionar antes de ela chegar, no que é cíclico |
| Por que uma regra não bastaria? | para a campanha anual, uma regra basta e é melhor — é a ação programada deste módulo. Para o ciclo diário e semanal, a regra teria de ser reescrita a cada mudança de comportamento do público, e é aí que a previsão ganha |
| De onde vêm os dados? | métricas do CloudWatch que já existem; a escala preditiva exige no mínimo 24 h de histórico e analisa até 14 dias |
| O que acontece quando ela erra? | nas duas direções: capacidade paga sem demanda, ou demanda sem capacidade. Por isso ela roda em modo de previsão primeiro, e por isso ela sozinha não reduz capacidade — a descida continua sendo da política reativa |
| Onde IA seria decoração? | gerar o valor do alvo, interpretar o gráfico do evento ou classificar o incidente. As três têm resposta exata por medição, e substituir medição por inferência é trocar certeza por latência |
| Por que não agora, na Cadência? | porque a campanha de aniversário acontece uma vez por ano. Não é padrão diário nem semanal, e um previsor treinado nisso não teria o que aprender |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo de linguagem para ler o painel e decidir se deve escalar. O sinal aqui é exato, numérico e chega em 20 segundos; qualquer inferência acrescenta latência ao caminho mais sensível a latência de toda a arquitetura, e acrescenta a chance de errar com confiança. Modelo de linguagem tem lugar quando o sinal é ambíguo ou volumoso demais para regra — investigar um incidente depois, correlacionar log com mudança — e não no laço de controle. Este é o mesmo raciocínio do L03, e ele não muda por o assunto ser capacidade.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Target tracking em CPU sem testar proporcionalidade | é o exemplo de toda documentação introdutória e a única métrica que todo mundo conhece | numa aplicação que espera dependência, a CPU não é proporcional à capacidade, e a política nunca dispara | latência em despenhadeiro com CPU plana; nenhuma atividade de escala em 20 min de pico | aplicar o teste das duas medições e escolher a métrica que passa nele | serviço genuinamente limitado por CPU — e aí é a escolha certa, comprovada pelo mesmo teste |
| Escolher o alvo por sensação, sem teto medido por task | medir dá trabalho e "70% parece razoável" | o alvo não tem relação com o que a task aguenta, então ele erra nas duas direções sem avisar | ou escala demais e a fatura sobe, ou escala de menos e a latência sobe | derivar de dois terços do teto medido, e refazer a medição quando a aplicação mudar | nunca para a métrica de regime; aceitável no piso de segurança, que é redundância |
| Ler `ALBRequestCountPerTarget` como requisições por segundo | quase toda métrica de taxa que se usa no dia a dia é por segundo | erra o alvo por um fator de 60: um alvo de 20 quando se queria 1.200 faz a política escalar ao máximo | a frota vai ao teto no primeiro cliente e fica lá | a métrica é por MINUTO e por alvo; 30 req/s são 1.800 por minuto | nunca; é erro de unidade, e ele não tem versão aceitável |
| Montar o `resource_label` com o ARN completo | é o campo que parece pedir um ARN, e o Terraform aceita qualquer string | a política é criada com sucesso e o alarme fica sem dado; nada escala e nada reclama | escala automática que nunca age, sem erro em log nenhum | concatenar os dois sufixos com barra, usando `arn_suffix` dos dois recursos | nunca; confira com `describe-scaling-policies` depois de aplicar |
| Deixar o cooldown de subida no padrão de 300 s | ninguém sabe que o padrão para ECS é 300, e o campo não é obrigatório | com rampa de 90 s, permite uma leva de capacidade e depois cinco minutos de espera | a capacidade sobe um degrau e para, com a latência ainda alta | cooldown de subida curto (60 s) e de descida longo; as duas intenções são opostas | carga que sobe em horas, onde uma leva por cooldown é suficiente |
| `max_capacity` escolhido por conforto | é um limite de segurança, então "põe alto que não atrapalha" | a política escala corretamente até derrubar a camada de baixo, e piora o incidente ao funcionar | latência que sobe COM a escala; erro de conexão recusada substituindo o 504 | derivar do menor teto de baixo: conexões do banco, cota da conta, endereços da sub-rede | quando a camada de baixo é elástica de verdade e o teto foi verificado, não presumido |
| Ação programada que sobe sem a que desce | a que sobe tem urgência e a que desce não tem nenhuma | o piso elevado passa a valer para sempre, e nada no sistema considera isso um defeito | fatura de Fargate constante e alta, sem nenhum alarme disparado | o par entra no mesmo commit, sempre; e a prova 5 confere que o piso voltou | nunca; é o custo oculto mais comum deste laboratório |
| Rodar o teste de carga com a escala ligada | é o estado normal do ambiente e desligar dá um passo a mais | você mede o conjunto e não a peça: o teto por task sai inflado pelo número de tasks que subiram | o alvo derivado fica alto demais e a política escala tarde em todo evento | suspender a escala nas fases 1 e 2, religar na fase 3 — e conferir que religou | na fase 3, que é justamente o teste do conjunto |
| Misturar step scaling e target tracking sem margem | os dois estão disponíveis e parece que somam proteção | se o step desce antes de o target tracking estar pronto, a descida não é bloqueada e a capacidade oscila | dente de serra no platô, com custo e latência oscilando em contrafase | step só para subida, com limiar bem acima do alvo; uma única política responsável pela descida | quando o step cuida da descida e o target tracking tem `disable_scale_in` ligado — o que é desenho, não acidente |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Nenhuma atividade de escala durante o pico | a métrica nunca cruza o alvo, ou o alarme está sem dado | veja se a lista de atividades está vazia e depois o estado do alarme | `describe-scaling-activities` e `describe-alarms` | se o alarme está em `INSUFFICIENT_DATA`, é o rótulo do recurso; se em `OK`, é a métrica |
| O alarme fica em `INSUFFICIENT_DATA` para sempre | `resource_label` malformado, ou métrica de alta resolução sem a configuração no serviço | confira o rótulo gravado e a existência de pontos no período esperado | `describe-scaling-policies` e `get-metric-statistics` com o período da política | rótulo como concatenação dos dois sufixos; ou aplicar `resolutionSeconds = 20` antes da política |
| A frota vai ao teto no primeiro cliente | alvo lido como por segundo em vez de por minuto | divida a chegada real por minuto pelo alvo configurado e veja se dá o teto | `RequestCountPerTarget` contra `target_value` | alvo em requisições por minuto por alvo; 30 req/s são 1.800 |
| Capacidade em dente de serra no platô | margem insuficiente entre subida e descida, ou duas políticas decidindo a descida | conte mudanças de direção por hora nas atividades de escala | `describe-scaling-activities` ordenado por hora | cooldown de descida maior que o de subida, margem entre limiares, uma única dona da descida |
| A latência sobe junto com a escala | o gargalo está na camada de baixo, e cada task nova o agrava | compare conexões do banco com o teto e a CPU do banco com a das tasks | `DatabaseConnections` e `CPUUtilization` do RDS | baixar `max_capacity` agora; mover o teto de verdade é o L07 e o L13 |
| A escala não desce depois do pico | ação programada elevou o piso e não voltou, ou uma segunda política discorda | leia os limites atuais do alvo escalável e as ações programadas registradas | `describe-scalable-targets` e `describe-scheduled-actions` | a ação que desce; e lembrar que, com duas políticas, a descida exige acordo de ambas |
| Os pontos de métrica voltaram a 60 s | uma implantação sem a configuração de monitoramento removeu a resolução de 20 s | conte pontos num período de 20 s e leia a revisão ativa do serviço | `describe-service-revisions` no campo de monitoramento | reaplicar a configuração; e mover isso para o IaC para não divergir de novo |
| A política escala durante o deploy sem carga nova | o denominador de `RequestCountPerTarget` mudou com os alvos do rollout | compare a chegada total do grupo com a chegada por alvo no intervalo do rollout | `RequestCount` e `RequestCountPerTarget` lado a lado | é comportamento esperado; se precisar impedir, suspender a escala de propósito durante a janela |
| `describe-scaling-activities` mostra sucesso e a capacidade não apareceu | a atividade termina na decisão; faltam a partida da task e o health check | compare o `EndTime` da atividade com a subida de `HealthyHostCount` | atividade de escala contra `HealthyHostCount` do grupo | nada a corrigir na política: os dois atrasos são físicos, e o do health check é o do L03 |
A pergunta que resolve metade destes casos
Antes de mexer em qualquer valor, pergunte: a política DECIDIU? Se a lista de atividades de escala está vazia, o problema está antes da decisão — na métrica, no rótulo do recurso ou no alarme — e nenhum ajuste de alvo, cooldown ou degrau vai ajudar. Se ela decidiu e a capacidade demorou, o problema está depois da decisão, na partida e no health check. Essas duas metades têm causas disjuntas, e tratar as duas como "a escala está lenta" é o que faz alguém passar a tarde no parâmetro errado.
Limpeza: o que o destroy não leva
Este laboratório cria pouco recurso e deixa duas armadilhas de custo que nenhuma delas é óbvia: um piso elevado que ninguém devolveu, e uma configuração de métrica de alta resolução que sobrevive à política que a pedia. Devolva o piso ANTES do destroy.
# limpar.sh — a ordem importa, e o que sobra aqui cobra por hora
set -euo pipefail
PROJETO=ffv-lab
RES="service/${PROJETO}/${PROJETO}-api"
# 1. PRIMEIRO devolva o piso. Se voce apagar a acao programada que DESCE antes de
# ela rodar, o piso elevado da campanha fica valendo — e 14 tasks ligadas por
# um mes e a fatura surpresa mais comum deste laboratorio.
aws application-autoscaling register-scalable-target --service-namespace ecs \
--resource-id "$RES" --scalable-dimension ecs:service:DesiredCount \
--min-capacity 1 --max-capacity 2
# 2. Depois o Terraform. Ele remove politicas, acoes programadas, alarmes e o
# registro do alvo escalavel.
terraform destroy -auto-approve
# 3. ALVO ESCALAVEL ORFAO: se o destroy foi interrompido, o registro pode
# sobreviver. Ele nao cobra nada por si, mas mantem politicas ativas mexendo
# num servico que voce acha que abandonou.
aws application-autoscaling describe-scalable-targets --service-namespace ecs \
--query 'ScalableTargets[].ResourceId' --output table
aws application-autoscaling deregister-scalable-target --service-namespace ecs \
--resource-id "$RES" --scalable-dimension ecs:service:DesiredCount 2>/dev/null || true
# 4. ALARMES DO TARGET TRACKING: sao criados pela politica, nao pelo Terraform, e
# a remocao da politica costuma leva-los. "Costuma" nao e "sempre" quando
# houve interrupcao — confira por prefixo e apague o que sobrou.
aws cloudwatch describe-alarms --alarm-name-prefix TargetTracking \
--query 'MetricAlarms[].AlarmName' --output table
aws cloudwatch describe-alarms --alarm-name-prefix "${PROJETO}-api" \
--query 'MetricAlarms[].AlarmName' --output table
# 5. A CONFIGURACAO DE MONITORAMENTO DE 20 s continua no servico e continua
# cobrando metrica de alta resolucao enquanto o servico existir. Ela NAO sai
# com a politica de escala: sao objetos diferentes.
aws ecs update-service --cluster "$PROJETO" --service "${PROJETO}-api" \
--monitoring 'metricConfigurations=[]' 2>/dev/null || true
# 6. E o que veio do L01 e cobra por hora, ligado ou nao: ALB, RDS, NAT Gateway e
# o Elastic IP dele. O Elastic IP cobra justamente por NAO estar associado.
aws ec2 describe-addresses --query "Addresses[?AssociationId==null].PublicIp" --output table
# 7. Prova final: se o serviço ainda existe, ele tem de estar no desiredCount
# minimo — e nenhuma politica pode estar de pe para mudar isso.
aws ecs describe-services --cluster "$PROJETO" --services "${PROJETO}-api" \
--query 'services[0].{desejado:desiredCount,rodando:runningCount}' --output table
aws application-autoscaling describe-scaling-policies --service-namespace ecs \
--query 'length(ScalingPolicies)' --output text # esperado: 0
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Políticas de escala | sim | não | os alarmes gerenciados costumam ir com elas; confira por prefixo |
| Alvo escalável registrado | sim | não | se o destroy foi interrompido, ele sobrevive mantendo políticas ativas num serviço abandonado |
| Ações programadas | sim | não diretamente | mas o PISO que elas elevaram continua valendo, e ele cobra por hora de task |
| Piso elevado no alvo escalável | não é um recurso | sim, horas de Fargate | é um valor, não um objeto: apagar a ação programada não desfaz o efeito que ela já teve |
| Configuração de monitoramento de 20 s | não | sim, métrica de alta resolução | pertence ao serviço, não à política; sobrevive a remover toda a escala |
| Alarme próprio do degrau | sim se em Terraform | centavos por alarme-mês | alarme criado no console não aparece no estado |
| Tasks que a escala subiu | sim | sim, por segundo de vCPU e GB | o `destroy` do serviço as leva; interromper no meio deixa a frota inteira de pé |
| ALB, RDS, NAT e o Elastic IP (L01/L02) | sim | sim, por hora | vêm dos laboratórios anteriores; o Elastic IP cobra justamente por estar ocioso |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Não sei qual número subir | teto por task medido em teste de carga | o número vem de uma divisão, e a divisão precisa do teto — sem ele, é consenso |
| A escala está ligada e não dispara | `ALBRequestCountPerTarget` no lugar de CPU | a métrica precisa ser proporcional à capacidade, e o ALB garante isso por aritmética |
| A capacidade chega depois do pico | ação programada elevando o piso antes | nenhuma política reativa vence um atraso de 200 s contra uma rampa de 90 s |
| A subida às vezes é um degrau | step scaling em alarme próprio, com margem | target tracking é proporcional; degrau grande pede salto fixo |
| O primeiro atraso é longo | `resolutionSeconds = 20` no serviço | é a única das quatro parcelas que se compra com configuração |
| A capacidade oscila | cooldown de descida maior que o de subida | as duas direções têm intenções opostas, e o padrão de 300 s está certo só para uma delas |
| Uma AZ cai no meio do pico | piso de 4 tasks, não 2 | o mínimo do regime e o mínimo da resiliência são números diferentes |
| Escalar tasks derruba o banco | `max_capacity` derivado de `max_connections` | o máximo é o menor teto de baixo, e o pool por task é o multiplicador |
| "A escala funcionou" é opinião | o gráfico do evento em três séries | a área entre demanda e capacidade é um número, e números encerram discussões |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Pico previsível | ação programada, sem atraso nenhum | pico que excedeu a previsão — para isso fica a política reativa por cima |
| Pico imprevisto e gradual | target tracking na métrica de chegada | subida em segundos: os quatro atrasos somam mais de um minuto no melhor caso |
| Degrau súbito | step scaling com salto fixo | degrau de 20× em 10 s, que é limite da arquitetura síncrona e assunto do L22 |
| Carga por requisição que mudou | a segunda política, de utilização em 20 s | regressão de latência sem mudança de utilização — é alarme de p99, não escala |
| Oscilação de capacidade | cooldowns assimétricos e margem entre limiares | duas políticas discordando na descida, que exige acordo de ambas |
| Queda da camada de baixo | `max_capacity` derivado do teto de conexões | o gargalo em si; movê-lo é o L07 e o L13, e nomeá-lo é o L40 |
- O teste de carga com a frota travada em 1 task devolve o teto: 30 req/s dentro do SLO.
- A mesma carga com 2 tasks reprova a CPU e aprova a chegada por alvo — é o teste de proporcionalidade.
- Dois terços do teto viram o alvo: 1.200 requisições por minuto por alvo.
- O `max_connections` lido do banco, dividido pelo pool por task, vira o `max_capacity` de 40.
- O alvo escalável registra os limites; as duas políticas de target tracking se penduram nele.
- O alarme próprio do degrau dispara a política de step scaling quando a chegada dobra o alvo.
- A ação programada eleva o piso para 14 quinze minutos antes do e-mail da campanha.
- A rampa chega; a política decide em cerca de 2 min, e a capacidade serve depois da partida e do health check.
- A segunda ação programada devolve o piso a 4 duas horas depois, e é ela que ninguém escreve.
- O gráfico do evento mostra a corcova de p99 fechando, e a área dela é a nota do laboratório.
Perguntas frequentes
❓ Por que a escala automática do ECS não dispara mesmo com a latência alta?
❓ Qual a diferença entre target tracking e step scaling no Amazon ECS?
❓ O que é ALBRequestCountPerTarget e qual valor devo usar como alvo?
❓ Quanto tempo o ECS leva para escalar depois que a carga chega?
❓ Devo escalar o ECS por CPU, memória ou número de requisições?
❓ Como escalar o ECS antes de um pico com hora marcada, como uma campanha?
❓ Escalar tasks no ECS pode derrubar o banco de dados?
❓ Escala preditiva do ECS vale a pena, ou basta target tracking?
Fixando
Você configurou target tracking em `ALBRequestCountPerTarget` com `target_value = 20`, porque mediu que uma task sustenta 20 requisições por segundo. No primeiro cliente da manhã a frota vai ao `max_capacity` e fica lá. Por quê?
A campanha começa às 19h com rampa de 90 s até 10× o tráfego. Você mediu o atraso total do desenho reativo em cerca de 200 s. Qual é a resposta correta, e por quê?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L01 no ar (ECS Fargate, RDS privado, ALB) e L03 concluído — o health check em 2 × 5 s daquele módulo é uma das quatro parcelas de atraso deste |
| Conhecimentos adquiridos | o teste de proporcionalidade como critério de escolha de métrica; os quatro atrasos e qual deles se compra com configuração; a unidade e o rótulo de recurso de `ALBRequestCountPerTarget`; a divisão entre capacidade reativa e planejada; e o `max_capacity` como o menor teto de baixo |
| Limitação que fica | o teto por task é uma medição com data de validade, e nada avisa quando ele cai; e o gargalo real da arquitetura foi evitado pelo teto, não encontrado |
| Próximo exemplo recomendado | L07 — o banco sob carga: réplica, Multi-AZ e o pool. É onde o teto de 40 tasks deixa de ser um muro, e ele usa o número de conexões calculado aqui |
| Também habilitado por este módulo | L40 (teste de carga até a quebra, com o gargalo nomeado) e L57 (derrubar uma AZ de propósito) dependem da política de escala e do gráfico do evento construídos aqui; L52 usa o p99 como SLI; L22 é a resposta para o degrau que a escala não alcança |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: Target tracking scaling policies for Application Auto Scaling e a página de como ela funciona — origem da assimetria entre subida e descida, do limite de cerca de 10% abaixo do alvo para descer, dos padrões de cooldown de 300 s para ECS e do comportamento com múltiplas políticas; Step scaling policies for Application Auto Scaling — tipos de ajuste, limites relativos dos degraus e a recomendação de quando usar cada tipo; Predefined metrics for target tracking scaling policies e PredefinedMetricSpecification — os nomes exatos das cinco métricas predefinidas de ECS e o formato do rótulo de recurso; Automatically scale your Amazon ECS service e Optimizing Amazon ECS service auto scaling — o intervalo de 1 minuto, o desligamento da descida durante implantação, as duas propriedades exigidas de uma métrica de escala e os padrões de aplicação, incluindo a recomendação de não escalar aplicação com coletor de lixo por memória; Amazon ECS service utilization metrics e Faster auto scaling with high-resolution metrics — a fórmula da utilização, o `resolutionSeconds = 20` e as restrições dele; CloudWatch metrics for your Application Load Balancer — a definição de `RequestCountPerTarget` e a estatística válida; Use historical patterns to scale Amazon ECS services with predictive scaling — as 24 h mínimas de histórico e o modo somente de previsão. Preços não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que NÃO foi verificado, e você deve medir na sua conta
Três coisas. Primeira: quantos períodos consecutivos o alarme gerenciado do target tracking exige antes de agir não está publicado para o Application Auto Scaling — o que este módulo faz é ensinar a MEDIR isso com `describe-scaling-activities` contra o início da rampa, e o valor de cerca de 90 s citado é o do evento de exemplo, não um número da documentação. Segunda: os tempos de partida de task (cerca de 40 s) e o teto por task (30 requisições por segundo com p99 de 780 ms) são medições da aplicação de exemplo e servem como ordem de grandeza; o seu teto sai do seu teste de carga, e ele muda quando uma consulta muda. Terceira: se o provedor Terraform da sua versão já expõe a configuração de monitoramento de 20 segundos como bloco em `aws_ecs_service` não foi confirmado — o caminho verificado em agosto de 2026 é o da CLI, e configuração fora do IaC volta a divergir, então confira antes de deixar assim.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…