Lab 15 — Aurora de verdade: endpoint, Serverless v2 e failover
O problema, e a empresa que o tem
O relatório de faturamento do L14 resolveu a disputa entre duas leituras opostas. O que ele não tocou é o tamanho do cluster Aurora por trás das duas: writer e reader nasceram `db.r6g.large`, fixos, e continuam assim — porque nenhum requisito daquele laboratório pedia outra coisa. Este é o requisito que apareceu depois.
A Cadência roda campanhas trimestrais que multiplicam o volume de pedidos por vinte em relação à madrugada. O time de operação mediu `CPUUtilization` por uma semana inteira: 4% de média entre 2h e 5h, picos de 82% durante os quarenta minutos de cada onda de campanha, três vezes ao dia nesses períodos. A instância que sobrevive ao pico é a mesma instância que cobra a hora inteira da madrugada vazia.
A segunda metade do problema é operacional, não financeira: ninguém jamais testou o que acontece se o writer cair. Existe um reader de pé desde o L14, mas "existe um reader" e "sabemos quanto tempo leva para ele assumir, medido, com carga real em cima" são afirmações muito diferentes — e só a segunda é uma garantia.
O que este laboratório NÃO é
Não é onde se explica a arquitetura de armazenamento compartilhado do Aurora nem a diferença entre writer e reader — isso o L14 já fez, e reler aqui seria repetição, não aprofundamento. Também não é sobre modelagem de tabela única no DynamoDB (L16) nem sobre disaster recovery multi-região (L58, que depende deste). Aqui a pergunta é específica: como decidir piso e teto de capacidade por medição, os dois endpoints e o erro de trocá-los, e um failover cronometrado.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com uma medição ou um comando na seção de implantação, não com a sensação de ter entendido.
- Medir a curva de capacidade real de um cluster Aurora por uma janela representativa, antes de decidir qualquer número.
- Explicar por que "scaling point" é um conceito do Serverless v1, e por que o v2 escala com transação em andamento, tabela travada e tabela temporária em uso.
- Derivar o piso de ACU do conjunto de dados quente, e o teto do pico medido com margem explícita — não de um valor copiado.
- Distinguir o cluster endpoint do reader endpoint, e explicar o que acontece ao tentar escrever pelo segundo.
- Explicar por que o failover do Aurora não espera uma réplica "alcançar" o writer, a partir da arquitetura de volume compartilhado.
- Configurar o tier de promoção de um reader para que ele já nasça do tamanho do writer.
- Ensaiar um failover com carga real em andamento e medir o tempo de indisponibilidade.
- Configurar um alarme de orçamento que impeça o teto de ACU virar um cheque em branco.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Unidade de capacidade do Aurora Serverless v2 | SAA-C03, SAP-C02 | ACU como memória + CPU proporcional, faixa configurável | que capacidade e armazenamento são dimensões separadas — o volume não encolhe quando o ACU cai |
| Escala contínua vs. degrau de instância | SAA-C03, DVA-C02 | incrementos a partir de 0,5 ACU vs. troca de classe | por que redimensionar um cluster provisionado é operação de manutenção, e Serverless v2 não é |
| "Scaling point" — o mito do v1 aplicado ao v2 | SAP-C02 | a explicação de por que o v2 não espera ponto de quietude | não confundir o comportamento documentado do Serverless v1 com o do v2 |
| Volume de cluster compartilhado | SAA-C03, SAP-C02 | writer e reader lendo o MESMO volume, sem cópia | por que adicionar um reader é instantâneo e por que o failover não espera replicação alcançar |
| Cluster endpoint vs. reader endpoint | SAA-C03, DVA-C02 | um `NpgsqlDataSource` por endpoint, nunca compartilhado | o cluster endpoint sempre aponta para o writer atual; o reader distribui entre leitoras |
| Tier de promoção de réplica | SAP-C02 | reader em tier 0 acompanhando a capacidade do writer | a diferença entre um reader pronto para assumir e um que precisa escalar depois de promovido |
| Tempo típico de failover do Aurora | SAA-C03, SOA-C02 | ensaio cronometrado, comparado ao valor documentado | por que Aurora recupera mais rápido que Multi-AZ clássico — a causa é o armazenamento, não mágica |
| Piso e teto de ACU | SAP-C02, DOP-C02 | derivados de `ServerlessDatabaseCapacity` medido, não de tabela de preço | o piso evita esvaziar o buffer cache; o teto evita fatura sem controle |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve uma carga variável e oferece "aumente o `max_capacity` bastante para não travar durante transação longa" como opção de resposta. A armadilha nomeia um comportamento real do Serverless v1 e o aplica ao v2, onde ele não existe: a documentação atual é explícita que a escala acontece com transação em andamento, sem esperar um ponto de quietude. A resposta certa é dimensionar o teto pelo pico medido, não pelo medo de travar.
Requisitos, e como cada um muda o desenho
Requisito que não aparece numa linha de configuração é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| A fatura deve acompanhar a carga | capacidade em ACU, não classe fixa | troca `db.r6g.large` por `db.serverless` no writer e no reader |
| Amplitude de carga | 20× entre madrugada e pico de campanha | exige medir a curva antes de fixar piso e teto — não adivinhar por instinto |
| Latência do histórico não pode regredir | p95 abaixo de 30 ms, herdado do L14 | piso de ACU dimensionado para caber o índice quente no buffer, não para ser o menor possível |
| Failover com tempo conhecido | medido, não estimado | reader em tier de promoção 0, e um ensaio de failover com carga real em andamento |
| Sem estouro de orçamento por query ruim | teto de ACU com alarme de custo | orçamento (Budgets) e alarme de `ACUUtilization` sustentada, além do teto técnico |
| Equipe de duas pessoas, sem tuning manual constante | nenhuma intervenção por campanha | a escala é automática dentro da faixa; ninguém redimensiona instância antes de cada evento |
| Aplicação já usa dois pools de conexão | writer e reader separados, desde o L14 | a troca de endpoint não muda código de aplicação além da string de conexão |
| Rastreabilidade da decisão de capacidade | piso e teto justificados por número | a curva de `ServerlessDatabaseCapacity` medida entra na documentação da decisão |
Arquitetura mínima: um Aurora, tamanho do pico, ligado a noite inteira
Este é o cluster que a Cadência tem hoje, herdado do L14. Ele é legítimo como ponto de partida: sobrevive à campanha, e a interface SQL não muda uma linha em relação a RDS. O laboratório começa medindo o custo da capacidade ociosa, porque um número torna o defeito discutível, e "está caro" não.
- → INSERT e UPDATE de pedidos, 24 horas por dia
- → SELECT do relatório de faturamento
- → CPUUtilization e FreeableMemory do writer
- → CPUUtilization e AuroraReplicaLag do reader
- Compute
- Banco de dados
- Gestão e governança
Este é o cluster que o L14 deixou no ar: writer e reader `db.r6g.large`, fixos, 24 horas por dia. Ele funciona — nenhuma peça está mal configurada. O defeito é que a instância foi dimensionada para os quarenta minutos de campanha às 14h, e ela cobra esse tamanho também às 3h, quando praticamente ninguém compra. Percorra os passos: a fatura não sabe que horas são.
- A capacidade foi dimensionada para o pico, não para o dia. `db.r6g.large` tem 2 vCPU e 16 GiB de memória — o suficiente para não cair durante os quarenta minutos de campanha. É uma decisão correta olhando só para o pico, e é exatamente por isso que ninguém a questionou até agora.
- Às 3 da manhã, a fatura não sabe que ninguém está comprando. A instância continua com 2 vCPU e 16 GiB reservados, cobrando o mesmo valor por hora que cobra às 14h. O `CPUUtilization` médio de madrugada gira em 4%: mais de 90% da capacidade paga fica ociosa na maior parte do dia.
- O reader paga o mesmo preço mesmo quando o relatório não roda. O reader existe para isolar o relatório de faturamento do L14, e ele é necessário durante o expediente. Fora dele, continua de pé, do mesmo tamanho, cobrando a mesma hora — porque instância provisionada não tem estado "ocioso e barato".
- O defeito não é falha de configuração: é aritmética de classe fixa. Uma classe de instância é um contrato de tamanho constante. Não existe meio termo entre "grande o bastante para o pico" e "pequeno o bastante para a madrugada" numa única classe — o intervalo entre os dois regimes é de vinte vezes, e a classe só tem um valor.
- Trocar de classe também não é grátis nem instantâneo. Redimensionar um cluster provisionado significa mudar para outra classe da família — `r6g.large` para `r6g.xlarge`, por exemplo, dobrando de capacidade de uma vez — e isso é uma operação de manutenção, não um botão de escala contínua. Alguém precisaria lembrar de fazer isso antes de cada campanha e desfazer depois.
- Por que a Cadência está exatamente aqui. Porque o L14 resolveu um problema diferente — qual banco serve qual consulta — e resolveu bem. Capacidade fixa nunca foi a pergunta daquele laboratório. Ela virou pergunta agora que a amplitude entre madrugada e campanha ficou grande o bastante para aparecer na fatura como linha visível.
A medição roda por uma janela representativa — o padrão da casa é uma semana, para cobrir os ciclos de campanha sem depender de um único dia atípico.
# Rode por pelo menos 7 dias corridos antes de decidir piso e teto.
CLUSTER=ffv-lab-aurora
INSTANCIA_WRITER=ffv-lab-aurora-writer
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS --metric-name CPUUtilization \
--dimensions Name=DBInstanceIdentifier,Value="$INSTANCIA_WRITER" \
--start-time "$(date -u -d '7 days ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 3600 --statistics Average Maximum --output table
# Na Cadencia: media de 4% entre 2h e 5h, picos de 82% em tres janelas de
# 40 min por dia de campanha. A instancia e a MESMA nos dois horarios — o
# preco por hora tambem e o mesmo, e e essa igualdade que este modulo quebra.A fatura que não sabe que horas são
Uma instância `db.r6g.large` cobra o mesmo valor por hora às 3h e às 14h, porque o contrato de capacidade provisionada é constante — não existe estado "ocioso e barato" numa classe fixa. Com CPU médio de 4% em boa parte do dia, a Cadência paga por capacidade que simplesmente não é usada na maior parte das 24 horas. Não é um erro de configuração: é a natureza do modelo provisionado aplicada a uma carga que varia vinte vezes.
Arquitetura para produção: capacidade derivada, reader pronto para assumir
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. Se você não conseguir apontar o requisito, a peça é adorno — e este desenho não tem nenhuma.
- → cluster endpoint — sempre o writer atual
- → reader endpoint — round-robin entre leitoras disponíveis
- → mesmo volume de cluster; réplica de storage, não de WAL
- → ServerlessDatabaseCapacity do writer, medida a cada segundo
- → ServerlessDatabaseCapacity do reader, medida a cada segundo
- Compute
- Banco de dados
- Gestão e governança
Não é o desenho anterior com um rótulo "serverless" colado: writer e reader trocaram de tipo de instância — `db.serverless` — e passaram a ter um piso e um teto de ACU derivados de medição, não da classe mais próxima. O reader entra em tier de promoção 0, o que muda o que ele faz num failover. Percorra os passos: cada peça nova rastreia a um requisito da tabela anterior.
- O piso de ACU não é zero: é o que mantém o índice quente. Zero ACU parece a economia óbvia, mas descarta o buffer cache a cada ociosidade — e o índice do histórico do L14, que respondia em 24 ms, volta a ser lido do disco até se aquecer de novo. O piso de 2 ACU (≈4 GiB) foi escolhido para caber o conjunto de dados quente, não por ser um número redondo.
- O teto vem do pico medido, com folga — não de um número redondo. Uma semana de `ServerlessDatabaseCapacity` mostrou pico de campanha em torno de 15 ACU. O teto de 32 ACU dá margem para crescimento e para o dia em que a campanha vier maior, sem herdar o hábito de "escolher o dobro do que parece seguro".
- A escala é fina, não por degrau de família de instância. O Aurora Serverless v2 muda a capacidade em incrementos a partir de 0,5 ACU, e o incremento cresce com a capacidade atual — quanto maior o cluster já está, mais rápido ele sobe. É o oposto de trocar `r6g.large` por `r6g.xlarge`, que dobra de uma vez e exige uma operação de manutenção.
- O reader já lê o mesmo volume: não existe "alcançar" o writer. Réplica e writer se conectam ao MESMO volume de cluster — a replicação acontece na camada de armazenamento, não copiando linhas por WAL. É por isso que promover o reader a writer não espera nenhuma sincronização de dados.
- O reader promovido herda a capacidade do writer, não a própria. Em tier de promoção 0, o piso do reader passa a ser a capacidade ATUAL do writer — ele já está no tamanho certo para assumir a carga inteira no instante do failover, em vez de precisar escalar depois de já ser o writer.
- As duas curvas, lado a lado, são a prova de que funciona. O painel mostra `ServerlessDatabaseCapacity` do writer e do reader ao lado de `CPUUtilization`: quando a carga sobe, a capacidade sobe minutos depois — não antes, o que seria capacidade ociosa paga sem motivo.
- Escrever no endpoint de leitura falha, e falha do jeito certo. O reader endpoint distribui conexões entre leitoras, e cada uma delas está aberta em modo somente leitura no próprio PostgreSQL. Um `INSERT` ali não corrompe nada: o banco recusa a transação com um erro explícito, antes de qualquer escrita ser tentada.
A diferença estrutural em relação ao desenho anterior não é "a mesma instância com um nome novo": é que capacidade deixou de ser uma classe escolhida de uma lista e passou a ser um intervalo justificado por medição, com o writer e o reader escalando dentro dele de forma independente.
O ajuste com maior efeito por decisão tomada
Trocar `db.r6g.large` por `db.serverless` com piso de 2 ACU e teto de 32 ACU já resolve a maior parte do defeito medido na seção anterior, e não exige reescrever uma linha de SQL — a interface Postgres é a mesma. O tier de promoção 0 no reader custa mais um pouco de capacidade mínima constante, e é o que transforma "existe um reader" em "o reader está pronto para assumir agora".
Como funciona, da medição ao failover ensaiado
A ordem importa: medir antes de configurar, configurar antes de ensaiar. Pular a medição transforma piso e teto em chute com aparência de decisão.
O evento que o CloudWatch registra quando um failover acontece — disparado à mão ou por uma falha real — carrega o suficiente para acionar um alarme sem que ninguém precise olhar o console no momento.
O que o EventBridge entrega quando o Aurora promove um reader a writer. Vale assinar para saber de um failover automatico sem estar de plantao.
{
"source": "aws.rds",
"detail-type": "RDS DB Cluster Event",
"resources": ["arn:aws:rds:us-east-1:111122223333:cluster:ffv-lab-aurora"],
"detail": {
"EventCategories": ["failover"],
"SourceType": "CLUSTER",
"Message": "A Multi-AZ failover for cluster ffv-lab-aurora has completed.",
"Date": "2026-08-07T14:12:04.000Z"
}
}"Scaling point" é termo do Serverless v1 — e o v2 foi desenhado para não precisar dele
O Serverless v1 introduzia o conceito de "scaling point": um instante em que era seguro escalar, e que transação longa, tabela travada ou tabela temporária em uso podiam impedir de existir — nesse caso, o Aurora tentava repetidamente até achar um ponto de quietude, ou aplicava "force scaling" derrubando conexões e locks. A documentação atual do Serverless v2 é explícita no sentido oposto: a escala acontece com conexões abertas, transações em andamento, tabelas travadas e tabelas temporárias em uso, sem esperar um ponto de quietude e sem interromper operação nenhuma. Configurar um teto alto "para não travar durante minha transação longa" resolve um problema que o v2 não tem — e só infla a fatura.
O que continua real, e não é o mesmo problema: uma transação longa pode manter páginas presas no buffer e atrapalhar a decisão de ESCALAR PARA BAIXO, porque a carga aparente permanece alta enquanto a transação não termina. Isso afeta o custo — o cluster demora mais para descer — e não a disponibilidade, que é a distinção que a confusão com o v1 apaga.
As decisões, e o que se perde em cada uma
📋 Cluster Aurora writer + reader `db.r6g.large`, fixo, rodando 24 horas para suportar uma campanha que multiplica o volume por vinte durante quarenta minutos, três vezes ao dia, com CPU médio de 4% no resto do tempo. Duas pessoas mantêm o sistema, sem tuning manual antes de cada campanha.
O problema declarado tem duas partes, e o Serverless v2 resolve as duas sem pedir operação manual: a capacidade acompanha a carga automaticamente dentro da faixa configurada, então a madrugada deixa de pagar o tamanho do pico; e o reader em tier 0 nasce do tamanho do writer, então o failover não depende de alguém redimensionar nada às pressas. A equipe de duas pessoas não tem tempo para redimensionar instância antes de cada campanha — e esquecer de fazer isso uma vez é o incidente.
Alt: Redimensionar a instância manualmente antes e depois de cada campanha — Funciona enquanto alguém lembra, e falha exatamente no dia em que ninguém lembra. Redimensionar é uma operação de manutenção com breve interrupção, não um ajuste contínuo — e ainda assim é feito no calendário errado com frequência maior do que se admite.
Alt: Instância provisionada maior, sempre ligada, sizada para o dobro do pico atual — Resolve o risco de faltar capacidade e piora o problema original: agora a madrugada paga por uma instância AINDA maior, 24 horas por dia, para um evento que dura duas horas somadas no mês.
Alt: RDS Proxy na frente do cluster provisionado atual — Resolve esgotamento de conexão e acelera reconexão após failover, mas não toca o problema declarado — o tamanho da instância continua fixo e a fatura de madrugada continua igual. É complementar ao Serverless v2, não substituto (entra no nível 3 da evolução).
Alt: Implantação Multi-AZ de cluster (dois standbys legíveis) — Resolve um problema diferente — mais capacidade de leitura com redundância — e continua sendo capacidade PROVISIONADA e fixa. A amplitude de vinte vezes entre madrugada e pico permanece intocada.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Piso de ACU | 2, derivado do conjunto de dados quente | 0 (auto-pause); 0,5 (padrão "seguro") | mantém o índice do histórico no buffer cache sem reaquecer a cada ociosidade | nunca chega a 0 ACU: paga um piso constante mesmo de madrugada |
| Teto de ACU | 32, dobro do pico medido de 15 | igual ao pico medido, sem margem; um valor alto "para garantir" | cobre crescimento e uma campanha maior sem herdar o hábito de superdimensionar por medo | ainda pode não bastar numa campanha muito acima do padrão histórico — requer remedir |
| Tier de promoção do reader | 0 — acompanha a capacidade do writer | tier 2-15, escalando de forma independente | o reader já nasce do tamanho certo para assumir; não precisa escalar depois de promovido | paga capacidade mínima mais alta o tempo todo, mesmo fora de failover |
| Escala de instância vs. Serverless v2 | Serverless v2 no writer e no reader | provisionado maior, sempre ligado; redimensionamento manual por campanha | acompanha a carga sem operação humana e sem janela de manutenção para trocar classe | faixa de capacidade (0–256 ACU) é menor que o maior provisionado disponível — não serve a toda carga |
| Ensaio de failover | disparado manualmente, com carga real em andamento | confiar no valor documentado sem medir; testar sem carga | o tempo real depende de reconexão da aplicação, que só aparece sob carga | exige uma janela de manutenção ou ambiente de homologação equivalente à produção |
| Endpoint por tipo de operação | writer para escrita, reader para leitura, dois pools | um único endpoint para tudo | isola o caminho de escrita do de leitura e faz o erro de gravar no reader falhar cedo | mais uma string de conexão e um pool para manter coerentes entre ambientes |
A dívida que o tier de promoção 0 cria, e que este módulo paga em dinheiro, não em risco
Um reader em tier 0 nunca escala abaixo da capacidade do writer, mesmo em horários em que ninguém está lendo relatório nenhum. É uma troca explícita: mais previsibilidade de failover por mais capacidade mínima paga. Para um reader que existe só para um relatório noturno pouco crítico, tier 2 com escala independente pode custar menos e ainda ser aceitável — a decisão depende de quanto o failover rápido vale para você.
Construir: o cluster, com piso e teto declarados
A troca central deste laboratório é uma linha: `instance_class` deixa de ser uma classe da família `r6g` e passa a ser `db.serverless`, com a faixa de capacidade declarada no próprio cluster. O restante do Terraform do L14 — sub-rede, segurança, criptografia, backup — não muda.
# aurora-serverless.tf — troca db.r6g.large por db.serverless, com piso e teto
# derivados de medicao (ver medir-a-ociosidade.sh) e o reader em tier de promocao 0.
variable "acu_minimo" {
description = "Piso de ACU, derivado do conjunto de dados quente (indice do historico)"
type = number
default = 2 # ~4 GiB — cabe o indice do L14 sem descartar cache a cada ociosidade
}
variable "acu_maximo" {
description = "Teto de ACU, derivado do pico medido (15 ACU) com margem de 2x"
type = number
default = 32
}
resource "aws_rds_cluster" "pedidos" {
cluster_identifier = "${var.projeto}-aurora"
engine = "aurora-postgresql"
# 16.3+ e o piso de versao para a faixa 0-256 ACU; confira a sua antes de aplicar.
engine_version = "16.4"
database_name = "pedidos"
master_username = "app_admin"
manage_master_user_password = true
db_subnet_group_name = aws_db_subnet_group.privada.name
vpc_security_group_ids = [aws_security_group.banco.id]
storage_encrypted = true
kms_key_id = aws_kms_key.app.arn
backup_retention_period = 7
preferred_backup_window = "03:00-04:00"
# O piso e o teto do CLUSTER. Cada instancia (writer, reader) tambem escala
# dentro desta faixa — nao existe faixa separada por instancia.
serverlessv2_scaling_configuration {
min_capacity = var.acu_minimo
max_capacity = var.acu_maximo
}
final_snapshot_identifier = "${var.projeto}-aurora-final-${formatdate("YYYYMMDD", timestamp())}"
skip_final_snapshot = false
deletion_protection = true # removido explicitamente antes do destroy, de proposito
}
resource "aws_rds_cluster_instance" "writer" {
identifier = "${var.projeto}-aurora-writer"
cluster_identifier = aws_rds_cluster.pedidos.id
# A troca central deste modulo: nao ha mais classe fixa. `db.serverless` diz
# ao Aurora para usar a faixa declarada acima em vez de uma classe da familia.
instance_class = "db.serverless"
engine = aws_rds_cluster.pedidos.engine
engine_version = aws_rds_cluster.pedidos.engine_version
publicly_accessible = false
}
resource "aws_rds_cluster_instance" "reader" {
identifier = "${var.projeto}-aurora-reader"
cluster_identifier = aws_rds_cluster.pedidos.id
instance_class = "db.serverless"
engine = aws_rds_cluster.pedidos.engine
engine_version = aws_rds_cluster.pedidos.engine_version
publicly_accessible = false
# Tier 0: o piso deste reader passa a ser a capacidade ATUAL do writer, nao
# o var.acu_minimo do cluster. E o que o deixa pronto para assumir sem
# precisar escalar depois de ja ser o writer.
promotion_tier = 0
}
output "endpoint_escrita" {
value = aws_rds_cluster.pedidos.endpoint
description = "cluster endpoint — sempre aponta para o writer atual, mesmo apos failover"
}
output "endpoint_leitura" {
value = aws_rds_cluster.pedidos.reader_endpoint
description = "reader endpoint — round-robin entre leitoras disponiveis; NUNCA aceita escrita"
}
O piso de 0,5 ou 0 ACU é tentador, e a documentação da AWS avisa sobre isso
Zero ACU habilita a pausa automática — o cluster para de cobrar compute quando ocioso, e a próxima conexão espera o cluster retomar. Para um cluster com tráfego constante como o da Cadência, isso troca economia marginal por uma latência de retomada perceptível na primeira requisição do dia. O piso de 2 ACU deste módulo não é conservador por acaso: ele mantém o cluster sempre pronto, e o ganho de custo já vem do teto que deixou de ser pago 24 horas.
Construir: o alarme que impede o teto virar cheque em branco
O teto técnico de 32 ACU não é um teto de custo — ele autoriza o cluster a sustentar essa capacidade indefinidamente, e nada no Aurora impede uma consulta mal escrita de fazer exatamente isso. O alarme abaixo é a segunda linha de defesa, depois da revisão de código.
# alarmes-capacidade.tf — o teto tecnico (max_capacity) nao e um teto de custo.
# Uma query em laco pode sustentar 32 ACU por horas sem violar nenhum limite do
# Aurora — o alarme abaixo e o que transforma isso em um aviso, nao numa
# surpresa na fatura do mes seguinte.
resource "aws_cloudwatch_metric_alarm" "acu_sustentado_no_teto" {
alarm_name = "${var.projeto}-aurora-acu-no-teto"
namespace = "AWS/RDS"
metric_name = "ServerlessDatabaseCapacity"
statistic = "Average"
period = 300
evaluation_periods = 6 # 30 minutos sustentados — campanha legitima nao passa disso
threshold = var.acu_maximo * 0.9
comparison_operator = "GreaterThanOrEqualToThreshold"
treat_missing_data = "notBreaching"
dimensions = {
DBClusterIdentifier = aws_rds_cluster.pedidos.cluster_identifier
}
alarm_actions = [aws_sns_topic.alertas.arn]
}
# rds:FailoverDBCluster e escopavel por ARN — nao e o `*` que precisa de
# justificativa. cloudwatch:GetMetricData e as duas acoes de metrica abaixo
# NAO aceitam recurso especifico: sao operacoes de leitura sobre o namespace
# inteiro da conta, entao `Resource = "*"` aqui e a unica forma de conceder a
# acao, nao uma escolha por preguica.
data "aws_iam_policy_document" "operar_failover" {
statement {
effect = "Allow"
actions = ["rds:FailoverDBCluster", "rds:DescribeDBClusters"]
resources = [aws_rds_cluster.pedidos.arn]
}
statement {
effect = "Allow"
actions = ["cloudwatch:GetMetricData", "cloudwatch:GetMetricStatistics"]
resources = ["*"]
}
}
O `*` que aparece na política, e por que ele se justifica
`cloudwatch:GetMetricData` e `cloudwatch:GetMetricStatistics` são operações de LEITURA sobre métricas da conta inteira — elas não têm um ARN de recurso específico para restringir, ao contrário de `rds:FailoverDBCluster`, que é escopado ao ARN do cluster na mesma política. A regra não é "nunca use `*`" — é "todo `*` tem de ter uma frase explicando por que não pode ser mais estreito". Sem essa frase, é preguiça; com ela, é decisão.
Construir: dois pools, dois endpoints, uma pipeline de resiliência
O padrão de dois pools já existia desde o L14 — o que muda aqui é o motivo declarado: não é só isolar carga de leitura e escrita, é que o comportamento dos dois endpoints diverge durante um failover, e a aplicação precisa saber disso.
// DataSources.cs — dois pools, dois enderecos, e o motivo de nunca compartilhar
var builder = WebApplication.CreateBuilder(args);
// Escrita: sempre o CLUSTER endpoint. Ele resolve para o writer atual — antes
// e depois de qualquer failover, sem a aplicacao saber ou precisar saber.
var enderecoEscrita = builder.Configuration.GetConnectionString("AuroraEscrita")!;
builder.Services.AddNpgsqlDataSource(enderecoEscrita, name: "escrita", dataSourceBuilder =>
{
// Timeout de conexao curto: se o writer esta indisponivel (failover em
// andamento), falhar rapido e deixar o Polly (abaixo) decidir o retry e
// por o registrar como o e melhor do que ficar pendurado no pool.
dataSourceBuilder.ConnectionStringBuilder.Timeout = 5;
});
// Leitura: o READER endpoint. Ele distribui entre as leitoras disponiveis por
// DNS round-robin — e cada leitora esta aberta em modo somente leitura no
// proprio PostgreSQL, entao um INSERT aqui falha no banco, nao na aplicacao.
var enderecoLeitura = builder.Configuration.GetConnectionString("AuroraLeitura")!;
builder.Services.AddNpgsqlDataSource(enderecoLeitura, name: "leitura", dataSourceBuilder =>
{
dataSourceBuilder.ConnectionStringBuilder.Timeout = 5;
});
// Resiliencia com retry e jitter, especifica para o intervalo de failover.
// Sem jitter, todas as instancias do servico tentariam reconectar no MESMO
// instante apos a promocao do reader, multiplicando a carga de reconexao
// exatamente quando o writer novo mais precisa de folga.
builder.Services.AddResiliencePipeline("aurora-escrita", pb =>
{
pb.AddRetry(new Polly.Retry.RetryStrategyOptions
{
ShouldHandle = new Polly.PredicateBuilder().Handle<NpgsqlException>(),
MaxRetryAttempts = 4,
BackoffType = Polly.DelayBackoffType.Exponential,
UseJitter = true,
Delay = TimeSpan.FromMilliseconds(200),
});
});
var app = builder.Build();
app.Run();
Retry sem jitter transforma um failover em apagão coordenado
Se todas as réplicas do serviço reconectarem no mesmo instante — porque cada uma aplicou o mesmo backoff fixo — o writer recém-promovido recebe uma rajada de conexões simultâneas no pior momento possível, logo depois de assumir a carga inteira sozinho. O jitter espalha essas tentativas no tempo. É a mesma lição do L03 sobre retry sem backoff (L36 aprofunda), aplicada ao caso específico de reconexão pós-failover.
// PedidoRepositorio.cs — o caminho de escrita, com o pool de escrita e a
// pipeline de resiliencia amarrados desde a injecao de dependencia.
public sealed class PedidoRepositorio
{
private readonly NpgsqlDataSource _escrita;
private readonly ResiliencePipeline _resiliencia;
public PedidoRepositorio(
[FromKeyedServices("escrita")] NpgsqlDataSource escrita,
[FromKeyedServices("aurora-escrita")] ResiliencePipeline resiliencia)
{
_escrita = escrita;
_resiliencia = resiliencia;
}
public async Task<Guid> CriarAsync(NovoPedido pedido, CancellationToken ct) =>
await _resiliencia.ExecuteAsync(async token =>
{
// Se um failover estiver em andamento, a primeira tentativa aqui
// recebe uma excecao de conexao — nao um erro de negocio. A
// pipeline reexecuta com backoff; NAO reexecute manualmente por
// fora dela, ou o jitter perde o efeito.
await using var conexao = await _escrita.OpenConnectionAsync(token);
await using var transacao = await conexao.BeginTransactionAsync(token);
const string sql = """
INSERT INTO pedidos (id, cliente_id, loja_id, total, status, criado_em)
VALUES (@id, @cliente, @loja, @total, 'confirmado', now())
""";
await using var comando = new NpgsqlCommand(sql, conexao, transacao);
comando.Parameters.AddWithValue("id", pedido.Id);
comando.Parameters.AddWithValue("cliente", pedido.ClienteId);
comando.Parameters.AddWithValue("loja", pedido.LojaId);
comando.Parameters.AddWithValue("total", pedido.Total);
await comando.ExecuteNonQueryAsync(token);
await transacao.CommitAsync(token);
return pedido.Id;
}, ct);
}
// HistoricoRepositorio.cs — o caminho de leitura, deliberadamente incapaz de
// escrever: o pool aponta para o reader endpoint, e nao ha metodo de escrita
// nesta classe. O compilador nao impede um INSERT por engano; o Postgres impede.
public sealed class HistoricoRepositorio
{
private readonly NpgsqlDataSource _leitura;
public HistoricoRepositorio([FromKeyedServices("leitura")] NpgsqlDataSource leitura) =>
_leitura = leitura;
public async Task<IReadOnlyList<PedidoResumo>> PorClienteAsync(
Guid clienteId, int tamanho, CancellationToken ct)
{
await using var conexao = await _leitura.OpenConnectionAsync(ct);
const string sql = """
SELECT id, loja_id, total, status, criado_em
FROM pedidos
WHERE cliente_id = @cliente
ORDER BY criado_em DESC
LIMIT @tamanho
""";
await using var comando = new NpgsqlCommand(sql, conexao);
comando.Parameters.AddWithValue("cliente", clienteId);
comando.Parameters.AddWithValue("tamanho", tamanho);
var linhas = new List<PedidoResumo>();
await using var leitor = await comando.ExecuteReaderAsync(ct);
while (await leitor.ReadAsync(ct))
linhas.Add(new PedidoResumo(
leitor.GetGuid(0), leitor.GetGuid(1), leitor.GetDecimal(2),
leitor.GetString(3), leitor.GetDateTime(4)));
return linhas;
}
}
Implantar, medir e ensaiar o failover
#!/usr/bin/env bash
set -euo pipefail
# 1. Aplica a troca para Serverless v2 com o piso e o teto medidos.
terraform apply -var="acu_minimo=2" -var="acu_maximo=32" -auto-approve
# 2. Confirma a faixa de capacidade ATIVA no cluster.
aws rds describe-db-clusters --db-cluster-identifier ffv-lab-aurora \
--query "DBClusters[0].ServerlessV2ScalingConfiguration"
# 3. Confirma o tier de promocao do reader.
aws rds describe-db-instances --db-instance-identifier ffv-lab-aurora-reader \
--query "DBInstances[0].PromotionTier"Cinco provas. Nenhuma aceita "parece que escalou" ou "o failover pareceu rápido" como resultado — cada uma tem um número, e a quinta é a que mais gente pula.
# provas.sh — cinco medicoes; nenhuma conclusao vem de "parece que funcionou".
set -euo pipefail
CLUSTER=ffv-lab-aurora
# -- Prova 1: a capacidade acompanha a carga, nao fica fixa ------------------
# Gere trafego crescente por 10 min e observe ServerlessDatabaseCapacity subir
# em incrementos — nao em um unico salto.
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS --metric-name ServerlessDatabaseCapacity \
--dimensions Name=DBClusterIdentifier,Value="$CLUSTER" \
--start-time "$(date -u -d '15 min ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 60 --statistics Average Maximum --output table
# -- Prova 2: escrever no reader endpoint falha no banco, nao na aplicacao ---
# Deve falhar com erro de transacao somente leitura do proprio PostgreSQL.
ENDPOINT_LEITURA=$(terraform output -raw endpoint_leitura)
psql "host=$ENDPOINT_LEITURA dbname=pedidos user=app_admin sslmode=require" \
-c "INSERT INTO pedidos (id) VALUES (gen_random_uuid());" 2>&1 \
| grep -qi "read-only transaction" \
&& echo "OK: o reader recusou a escrita" \
|| echo "FALHA DA PROVA: a escrita nao deveria ter sido aceita"
# -- Prova 3: o failover ensaiado, com carga real em andamento ---------------
# Um laco de escrita roda enquanto o failover e disparado a mao. O que importa
# e o intervalo entre a ULTIMA escrita aceita e a PRIMEIRA aceita depois.
( i=0; while true; do
if psql "$(terraform output -raw endpoint_escrita)" -c \
"INSERT INTO pedidos (id) VALUES (gen_random_uuid());" >/dev/null 2>&1; then
echo "$(date +%T.%N) OK $i"
else
echo "$(date +%T.%N) FALHA $i"
fi
i=$((i+1)); sleep 0.2
done ) > /tmp/janela-failover.log &
LACO=$!
aws rds failover-db-cluster --db-cluster-identifier "$CLUSTER"
aws rds wait db-cluster-available --db-cluster-identifier "$CLUSTER"
sleep 30; kill $LACO
# Conte as linhas "FALHA" consecutivas no log — multiplicado por 0,2 s, isso
# e a janela de indisponibilidade medida. Na Cadencia: 9 falhas seguidas,
# cerca de 1,8 s — dentro dos "tipicamente abaixo de 30 s" da documentacao,
# mas o numero exato so existe porque foi medido, nao presumido.
grep -c FALHA /tmp/janela-failover.log || true
# -- Prova 4: o reader promovido ja nasceu do tamanho certo -------------------
# Apos o failover, confira que o NOVO writer (o reader promovido) nao passou
# por um salto de ACU logo em seguida — ele ja estava no tamanho do writer antigo.
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS --metric-name ServerlessDatabaseCapacity \
--dimensions Name=DBClusterIdentifier,Value="$CLUSTER" \
--start-time "$(date -u -d '5 min ago' +%FT%TZ)" --end-time "$(date -u +%FT%TZ)" \
--period 30 --statistics Average --output table
# -- Prova 5: o alarme de teto sustentado dispara de verdade ------------------
# Gere carga sintetica que sustente 90% do teto por 30 min (ou reduza o teto
# temporariamente para testar) e confirme o estado do alarme.
aws cloudwatch describe-alarms \
--alarm-names "ffv-lab-aurora-acu-no-teto" \
--query "MetricAlarms[0].StateValue" --output text| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · Capacidade acompanha carga | `ServerlessDatabaseCapacity` em janela de tráfego crescente | a curva sobe em incrementos, acompanhando a carga gerada | capacidade plana com carga variando indica que o piso e o teto estão iguais, ou muito próximos |
| 2 · Reader recusa escrita | INSERT via reader endpoint | erro de transação somente leitura do PostgreSQL | se a escrita for aceita, o endpoint usado não é o reader — confira a string de conexão |
| 3 · Janela de failover medida | laço de escrita durante `failover-db-cluster` | um número de segundos, não uma sensação | ausência de qualquer falha pode indicar que o laço não estava realmente tocando o writer |
| 4 · Reader promovido não salta de ACU | `ServerlessDatabaseCapacity` logo após o failover | a capacidade do novo writer já estava no patamar anterior | um salto grande após o failover indica tier de promoção diferente de 0 |
| 5 · Alarme de teto dispara | `describe-alarms` após carga sustentada | estado `ALARM` | estado `INSUFFICIENT_DATA` prolongado indica dimensão errada no alarme |
Quebrar de propósito: três falhas e o diagnóstico
As três acontecem de verdade na operação de um cluster Serverless v2, e as três se parecem no sintoma superficial — "o banco está lento" ou "o banco não responde". O que as separa é onde se olha.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Piso de ACU baixo demais para o conjunto quente | reduza `acu_minimo` para 0,5 num cluster com o índice do histórico grande | latência do histórico sobe de forma intermitente, mesmo sem pico de tráfego | `ServerlessDatabaseCapacity` oscilando perto do piso, correlacionado com `ReadLatency` | elevar o piso para caber o conjunto de dados quente, medido por `BufferCacheHitRatio` |
| Teto de ACU alcançado por consulta ruim, não por tráfego real | rode uma consulta sem índice sobre a tabela inteira em paralelo ao tráfego normal | `ACUUtilization` sustentado perto de 100%, latência de TODAS as consultas sobe junto | `Performance Insights` mostrando uma consulta dominando o tempo de espera | corrigir o índice ou mover a consulta para um reader isolado — não subir o teto |
| Aplicação sem retry após failover | dispare `failover-db-cluster` sem a pipeline de resiliência do C# ativa | uma rajada de exceções de conexão na aplicação, que não se recupera sozinha | log da aplicação mostrando `NpgsqlException` sem nova tentativa em seguida | pipeline de retry com backoff e jitter, como na seção de construção |
A falha que o alarme de ACU não pega
O alarme deste módulo observa capacidade SUSTENTADA perto do teto — ele não distingue tráfego real de consulta ruim. Uma campanha legítima e uma consulta sem índice acionam o MESMO alarme. É por isso que a prova 5 existe ao lado do `Performance Insights`: o alarme diz que algo está caro; só o `Performance Insights` diz o quê.
Seu cluster Aurora Serverless v2 atende tráfego constante e tem um índice quente de histórico de pedidos. Você configura o piso em 0,5 ACU "para economizar ao máximo". Por que isso é arriscado?
Segurança: o que muda quando a capacidade é elástica
Capacidade que escala sozinha desloca um risco que capacidade fixa não tinha: alguém com acesso de escrita pode gerar carga que a conta paga automaticamente, sem precisar pedir permissão para redimensionar nada.
| Risco | Probabilidade | Impacto | Controle preventivo | Detecção | Resposta |
|---|---|---|---|---|---|
| Consulta ou laço mal escrito sustenta o teto de ACU por horas | média | médio | alarme de `ACUUtilization` sustentada (seção de construção); revisão de índice em code review | alarme em `ALARM` combinado com `Performance Insights` | identificar a consulta dominante e corrigi-la ou isolá-la num reader |
| Failover disparado sem necessidade, gerando indisponibilidade evitável | baixa | médio | `rds:FailoverDBCluster` restrito por IAM a um papel de operação, não à role de deploy | CloudTrail em `FailoverDBCluster` fora de uma janela de ensaio programada | auditar quem disparou e por quê; o próprio failover já resolve a indisponibilidade em segundos |
| Escrita aceita pelo endpoint errado por engano de configuração | baixa | alto | o PostgreSQL recusa escrita em réplica somente leitura — é proteção do próprio banco | log de aplicação com erro de transação somente leitura em produção | corrigir a string de conexão; nenhum dado foi perdido, porque a escrita nunca foi aceita |
| Credencial mestra do cluster exposta | baixa | alto | `manage_master_user_password` — segredo gerado e rotacionado pelo Secrets Manager, herdado do L14 | CloudTrail em acesso ao segredo fora dos papéis esperados | rotacionar via Secrets Manager; a aplicação lê por ARN e não precisa de deploy novo |
O que o menor privilégio aqui separa, e por que separa
A ação de disparar um failover (`rds:FailoverDBCluster`) e a de aplicar mudança de infraestrutura (`terraform apply` sobre o cluster) não deveriam pertencer à mesma identidade em produção. Um ensaio de failover é uma operação de rotina que uma pessoa de plantão dispara sem tocar Terraform nenhum; separar os dois papéis evita que uma credencial de deploy vazada também controle a disponibilidade do banco.
Observabilidade: as perguntas que o painel de capacidade responde
Um painel de Aurora Serverless v2 tem uma função estreita: dizer se a capacidade está acompanhando a carga, e se um failover deixaria o sistema pronto. Métrica que não ajuda nessa decisão pertence a outro painel.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| A capacidade está acompanhando a carga? | `ServerlessDatabaseCapacity` do writer | sobe com tráfego, desce com ociosidade | variação diária visível; capacidade plana é suspeita |
| Estamos perto do teto por tráfego real ou por consulta ruim? | `ACUUtilization` + Performance Insights | utilização sustentada acima de 90% sem melhora de throughput indica gargalo, não capacidade | ≥ 90% por 30 min |
| O reader está pronto para assumir agora? | `ServerlessDatabaseCapacity` do reader vs. do writer | divergência grande indica tier de promoção diferente de 0 | diferença > 20% sustentada |
| O buffer cache está sofrendo com o piso atual? | `BufferCacheHitRatio` | queda after descida de capacidade indica piso baixo para o conjunto quente | < 95% fora de janela de escala |
| Um failover aconteceu, e quanto tempo levou? | evento `RDS DB Cluster Event` categoria `failover` | confirma failover automático e ancora a duração real observada | qualquer ocorrência fora de ensaio programado |
| O custo está dentro do esperado? | `ServerlessDatabaseCapacity` integrada no tempo (ACU-hora) | tendência muito acima do padrão histórico indica teto sendo usado sem necessidade | revisão semanal |
A métrica que engana logo depois do failover
`DatabaseConnections` cai a zero no instante da promoção e sobe de novo conforme a aplicação reconecta — um alarme ingênuo de "conexões em zero" dispararia em todo failover normal, inclusive nos ensaiados de propósito. O sinal que importa é a DURAÇÃO da queda, não a queda em si.
Escala: 10 lojas, 900, e a próxima ordem de grandeza
| Volume | O que acontece com a capacidade | O que passa a doer | O que fazer |
|---|---|---|---|
| 10 lojas, carga estável | o cluster passa a maior parte do tempo perto do piso | nada; é sub-utilização saudável de um piso bem dimensionado | nada |
| 900 lojas, pico de campanha 20× | capacidade sobe de ~2 para ~15 ACU em minutos | é o cenário deste laboratório | piso e teto medidos, como construído aqui |
| 9 mil lojas, pico ainda maior | o teto de 32 ACU pode não bastar mais | consultas que hoje cabem num reader único passam a competir por CPU | remedir o pico, considerar mais de um reader (até 15) para distribuir leitura |
| Pico fora do padrão sazonal (campanha maior que qualquer uma anterior) | capacidade sobe até o teto e para | requisições começam a esperar fila de CPU no writer, sem erro explícito | alarme de ACU no teto (seção de construção) avisa; ajuste o teto e remeça |
| Falha de AZ durante o pico | o reader em outra AZ assume via failover | a capacidade do writer novo é a do reader promovido, que pode ser menor que o pico | reader em tier 0 acompanha o writer; ainda assim, ensaie o failover DURANTE pico, não só em carga normal |
| Consulta com transação muito longa (minutos) | a capacidade tende a não descer enquanto ela roda | custo elevado por mais tempo que o necessário — não indisponibilidade | identificar e limitar transações longas com `statement_timeout`; não é o mesmo problema que "scaling point" |
O gargalo que só aparece acima de 15 réplicas
Aurora aceita até 15 réplicas por cluster. Uma frota de leitura desse tamanho normalmente indica que a carga de leitura deveria estar em outro lugar — cache (ElastiCache), busca dedicada (OpenSearch) ou uma segunda fonte de verdade — não que falta adicionar a réplica 16.
Custo: o que este laboratório muda na fatura
Este é o laboratório em que o custo é o próprio requisito, não um efeito colateral. O ganho vem de pagar pela curva em vez de pagar pelo pico o tempo todo.
| Cenário | Volume | O que muda na fatura | Tendência | Otimização |
|---|---|---|---|---|
| Antes (L14) | writer + reader `r6g.large`, 24h | duas instâncias de tamanho fixo, cobrando o pico o dia inteiro | constante, independente da carga | nenhuma sem trocar o modelo de capacidade |
| Depois, dia normal | piso 2 ACU na maior parte das 24h | ACU-hora próximo do piso na maior parte do tempo | acompanha a curva de CPU medida | revisar o piso se o conjunto quente crescer |
| Depois, dia de campanha | até 32 ACU por ~2 horas somadas no dia | ACU-hora mais alto só durante os picos reais | proporcional à duração da campanha, não ao dia inteiro | confirmar que o teto não é atingido por consulta ruim |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Capacidade Serverless v2 | ACU-hora, medida por segundo | o piso é pago o tempo todo — dimensione-o pelo conjunto quente, não por segurança extra |
| Armazenamento do volume de cluster | GB-mês, independente da capacidade de compute | não cai quando o ACU cai — compute e armazenamento são cobrados separadamente |
| Reader em tier de promoção 0 ou 1 | ACU-hora no mínimo igual ao do writer | é o preço explícito de prontidão para failover — tier mais alto custa menos e falha pior |
| I/O do volume (se Aurora Standard) | por milhão de requisições de I/O | Aurora I/O-Optimized remove essa cobrança e compensa quando I/O passa de 25% do gasto total |
| Backup além do período de retenção padrão | GB-mês retido | retenção de 7 dias herdada do L14; aumentar tem custo direto e proporcional |
O ganho de custo que a curva prova, não a intuição
Comparar ACU-hora integrado ao longo de uma semana antes e depois da migração é o único jeito honesto de saber quanto se economizou — "parece mais barato" não é número. Para a Cadência, o piso de 2 ACU na maior parte do dia contra duas instâncias `r6g.large` fixas é a diferença que sustenta a decisão deste módulo.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | piso e teto documentados com a medição que os gerou; failover ensaiado e cronometrado | o ensaio cobre uma falha controlada, não uma AZ inteira caindo de verdade | experimento de injeção de falha de AZ (L57) | média |
| Segurança | segredo mestre gerenciado, IAM separado entre failover e deploy | a política de failover ainda não distingue ensaio programado de disparo ad-hoc | janela de manutenção aprovada para ensaios (parte do L54) | baixa |
| Confiabilidade | reader em tier 0, pronto para assumir; failover medido, não estimado | um único reader é um único ponto de redundância — falha dupla simultânea ainda derruba | segundo reader em outra AZ para tolerar duas falhas | média |
| Eficiência de performance | capacidade acompanha a carga em incrementos finos, sem operação manual | consulta sem índice ainda consome ACU real antes de qualquer alarme disparar | revisão de índice como parte do processo de deploy, não reativa | média |
| Otimização de custos | paga pela curva medida, não pelo pico constante | o piso de 2 ACU é decisão de hoje; o conjunto quente cresce com o volume de pedidos | revisar o piso trimestralmente com o mesmo script de medição | alta |
| Sustentabilidade | capacidade ociosa reduzida de ~90% do tempo para o piso necessário | compute não usado ainda existe brevemente durante a descida da escala | já é o melhor disponível no modelo atual; sem ação adicional | 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 de novo. Cada nível resolve um risco e compra outro.
Aurora provisionado único, sem reader, capacidade pequena e fixa. É onde qualquer laboratório de banco começa.Serverless v2 com piso e teto derivados de medição, reader em tier de promoção 0, failover ensaiado com tempo cronometrado.Pool de conexões gerenciado entre a aplicação e o Aurora, com reconexão mais rápida após failover e proteção contra esgotamento de `max_connections` durante escala rápida.Um reader em tier 0 para HA (como aqui) e um ou mais readers em tier 2+ escalando de forma independente, dedicados a relatório pesado — sem competir por capacidade com o caminho de failover.Replicação assíncrona entre regiões, com uma região secundária pronta para assumir em caso de indisponibilidade regional (L58).Usar o histórico de `ServerlessDatabaseCapacity` e o calendário de campanhas para prever o próximo teto necessário antes de ele ser alcançado, em vez de reagir ao alarme.A ordem não é negociável, e o motivo é concreto
RDS Proxy no nível 3 resolve um problema que só aparece depois de a aplicação já depender de reconexão rápida pós-failover — que é o nível 2. Quem instala o proxy antes de medir piso e teto está resolvendo esgotamento de conexão de um cluster que ainda não sabe qual capacidade realmente precisa.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo, IA não decide o piso nem o teto de ACU, e forçá-la seria o antipadrão que a própria série critica. As duas perguntas centrais — "quanto de conjunto quente preciso manter aquecido" e "qual foi o pico real medido" — têm resposta por medição direta. Um modelo não melhora nenhuma das duas: elas são leitura de métrica e aritmética.
Há um lugar onde IA acrescentaria valor real, e ele é modesto: prever o TAMANHO da próxima campanha a partir do histórico de marketing e do padrão de campanhas anteriores, para revisar o teto ANTES do evento em vez de depois de um alarme. Isso é diferente de decidir a capacidade em tempo real — o Aurora já faz isso sozinho, em segundos, sem esperar previsão nenhuma.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | antecipar o tamanho de uma campanha específica antes de ela começar |
| Por que uma regra não bastaria? | uma regra simples — "revise o teto se a campanha anunciada supera a maior já medida" — cobre a maior parte dos casos; IA só se justificaria com histórico de dezenas de campanhas com características variadas |
| De onde viriam os dados? | `ServerlessDatabaseCapacity` histórico do CloudWatch, calendário de campanhas e volume de cupons emitidos, que já existem no sistema de marketing |
| Qual o risco? | prever teto abaixo do necessário por excesso de confiança no modelo, quando o alarme reativo já cobria o caso — a previsão não pode SUBSTITUIR o alarme, só antecipá-lo |
| Por que não agora? | a Cadência tem poucas campanhas medidas até aqui; um modelo treinado com menos de uma dezena de eventos é ajuste de curva, não previsão |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "decidir o teto de ACU em tempo real, com base no padrão de tráfego" troca um mecanismo determinístico e já rápido — o Aurora escala sozinho em segundos, sem esperar ponto de quietude nenhum — por um probabilístico e mais lento. Onde a escala automática já resolve o problema, IA só acrescenta latência de decisão e a chance de errar com confiança.
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 |
|---|---|---|---|---|---|
| Configurar o teto de ACU alto "para não travar durante transação longa" | confunde o comportamento do Serverless v1 (scaling points) com o v2 | o v2 não espera ponto de quietude — o teto alto não resolve nada que existisse, só paga mais | fatura de ACU-hora acima do que o tráfego medido justificaria | dimensionar o teto pelo pico medido, com margem declarada | nunca; é sempre um valor herdado de um medo que não se aplica ao v2 |
| Piso de ACU em 0 ou 0,5 num cluster de tráfego constante | parece a economia óbvia, e a documentação até menciona como tentador | descarta o buffer cache a cada ociosidade; a primeira consulta depois paga o preço do disco | latência intermitente sem relação aparente com volume de tráfego | piso dimensionado pelo conjunto de dados quente, medido por `BufferCacheHitRatio` | cluster genuinamente esporádico, sem consulta latência-sensível — dev/teste, por exemplo |
| Reader em tier de promoção 2+ quando failover rápido é requisito | custa menos capacidade mínima constante | o reader pode estar subdimensionado no instante do failover e precisar escalar depois de já ser writer | failover tecnicamente concluído, mas com latência elevada logo em seguida | tier 0 ou 1 quando a velocidade de failover é o requisito declarado | reader dedicado a relatório, onde failover rápido não é o papel dele |
| Aplicação sem retry após reconexão a um endpoint que mudou de writer | o cluster endpoint "parece" estável, então ninguém trata reconexão como caso esperado | a primeira exceção de conexão após um failover derruba a requisição sem tentar de novo | rajada de erro 500 durante e logo após um failover, mesmo bem-sucedido do lado do banco | pipeline de retry com backoff e jitter, amarrada ao pool de escrita | nunca; failover é evento esperado, não excepcional |
| Testar failover sem carga real em andamento | é mais simples disparar o comando e ver se o cluster volta | o tempo medido sem carga esconde o efeito de reconexão simultânea de múltiplas instâncias da aplicação | "o failover funcionou no teste" e depois demora visivelmente mais em produção | ensaiar com um laço de escrita real, como na seção de provas | primeira verificação rápida de que o mecanismo básico funciona, antes do ensaio completo |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Latência sobe sem relação com volume de tráfego | piso de ACU abaixo do conjunto de dados quente | compare quedas de `BufferCacheHitRatio` com o momento em que a capacidade desceu ao piso | `ServerlessDatabaseCapacity` vs. `BufferCacheHitRatio` | elevar o piso para caber o conjunto quente medido |
| Capacidade sustentada perto do teto sem pico de tráfego visível | consulta sem índice ou em laço consumindo ACU | abra o Performance Insights e ordene por tempo de espera | Performance Insights, `pg_stat_statements` | corrigir o índice; considerar mover a consulta para um reader isolado |
| Erro de "read-only transaction" em produção | a aplicação está escrevendo pelo reader endpoint | confira a string de conexão do caminho que gerou o erro | configuração da aplicação, não o banco | apontar o caminho de escrita para o cluster endpoint |
| Rajada de exceções de conexão logo após um failover | ausência de retry com backoff, ou backoff sem jitter | meça o intervalo entre as exceções e veja se são simultâneas entre instâncias | log da aplicação, timestamps correlacionados entre réplicas do serviço | pipeline de retry com backoff exponencial e jitter |
| Failover demora visivelmente mais que o documentado | reader em tier de promoção diferente de 0, ou ausência de reader | confira `PromotionTier` de todas as instâncias do cluster | `describe-db-instances` → `PromotionTier` | garantir ao menos um reader em tier 0 ou 1 em AZ diferente do writer |
| Fatura de ACU-hora muito acima do padrão histórico, sem campanha | teto atingido por engano de configuração ou consulta introduzida em deploy recente | compare `ServerlessDatabaseCapacity` integrado por semana contra o histórico | CloudWatch, correlacionado com o log de deploys | identificar o deploy que mudou o padrão de consulta e reverter ou corrigir |
A pergunta que resolve metade destes casos
Antes de mexer em piso ou teto, pergunte: a capacidade está subindo por TRÁFEGO ou por CONSULTA RUIM? `ServerlessDatabaseCapacity` sozinho não distingue os dois — só o Performance Insights, olhando o que está consumindo o tempo, separa "a campanha chegou" de "alguém introduziu uma varredura de tabela inteira".
Limpeza: o que o destroy não leva
A troca para Serverless v2 não acrescenta recurso novo além do que o L14 já criava — o que muda é a classe de instância e a configuração de escala. A limpeza herda os cuidados do L14, com um item adicional: os alarmes deste módulo.
# 1. Remova o deletion_protection ANTES do destroy — ele existe para impedir
# exclusao acidental, nao para bloquear uma limpeza deliberada.
terraform apply -var="deletion_protection=false" -auto-approve
# 2. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 3. SNAPSHOT FINAL: o destroy CRIA um, por causa de skip_final_snapshot=false.
# Ele sobrevive ao destroy de proposito, e cobra armazenamento enquanto existir.
aws rds describe-db-cluster-snapshots \
--query "DBClusterSnapshots[?contains(DBClusterSnapshotIdentifier, 'ffv-lab-aurora')].DBClusterSnapshotIdentifier" \
--output table
# Apague so depois de confirmar que ninguem precisa dele:
# aws rds delete-db-cluster-snapshot --db-cluster-snapshot-identifier <nome>
# 4. ALARMES criados fora do Terraform, se algum foi ajustado a mao no console.
aws cloudwatch delete-alarms --alarm-names ffv-lab-aurora-acu-no-teto 2>/dev/null || true
# 5. SEGREDO NO SECRETS MANAGER: o gerenciado pelo cluster tem exclusao propria,
# com periodo de retencao antes de apagar de verdade.
aws secretsmanager list-secrets \
--query "SecretList[?contains(Name, 'ffv-lab-aurora')].Name" --output table
# 6. Prova final: nada com o nome do projeto de pe, alem do snapshot retido.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=ffv-lab \
--query "ResourceTagMappingList[].ResourceARN" --output tableDesligar deletion_protection sem confirmar o snapshot é perda irreversível
Uma vez que o `terraform destroy` roda com `skip_final_snapshot = false`, o cluster some e o snapshot final é o ÚNICO caminho de volta. Se alguém apagar esse snapshot sem confirmar antes que não é mais necessário, os dados da Cadência não existem em lugar nenhum — não há segunda chance depois desse ponto. Confirme o nome do snapshot antes de qualquer `delete-db-cluster-snapshot`.
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Cluster e instâncias Serverless v2 | sim | não | compute para com o cluster |
| Snapshot final | não — é CRIADO pelo destroy | sim, GB-mês | existe justamente para sobreviver à exclusão; é a rede de segurança do próprio destroy |
| Segredo do Secrets Manager | depende da política de exclusão do recurso | sim, por segredo-mês durante o período de retenção | exclusão tem espera padrão antes de apagar de verdade — é proteção contra remoção acidental |
| Alarme de CloudWatch | sim, se declarado em Terraform | centavos | alarme ajustado à mão no console não aparece no estado do Terraform |
| Grupo de sub-rede e de segurança do L01/L14 | sim | não diretamente | mas outros recursos do L01 (ALB, NAT) continuam cobrando se você não seguiu a limpeza deles |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Fatura fixa para uma carga que varia 20× | `db.serverless` com piso e teto medidos | a capacidade acompanha a curva; ninguém paga o tamanho do pico às 3 da manhã |
| Medo de transação longa travando a escala | confirmação de que "scaling point" é do v1 | o v2 escala com transação em andamento — o teto não precisa ser inflado por esse medo |
| Piso baixo esvaziando o buffer cache | piso dimensionado pelo conjunto de dados quente | evita reaquecer o índice do histórico a cada ociosidade |
| Teto sem controle de custo | alarme de ACU sustentada perto do teto | separa tráfego real de consulta ruim antes de virar surpresa na fatura |
| Escrever no endpoint errado | dois pools, dois endpoints, sem compartilhamento | o cluster endpoint sempre é o writer atual; o reader recusa escrita no próprio banco |
| Failover com tempo desconhecido | ensaio cronometrado com carga real em andamento | transforma "deve ser rápido" em um número escrito e comparável ao documentado |
| Reader subdimensionado no instante do failover | tier de promoção 0 | o piso do reader acompanha a capacidade atual do writer, pronto para assumir |
| Rajada de reconexão derrubando o writer novo | retry com backoff e jitter | espalha as tentativas no tempo em vez de concentrá-las no pior momento |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Fatura de madrugada pagando o pico | capacidade Serverless v2 com piso baixo | consulta ruim sustentando o teto — isso é o alarme de ACU |
| Falha do writer | reader em tier 0, failover automático | falha simultânea de duas AZs — precisaria de segundo reader (L57/L58) |
| Escrita no endpoint errado | réplica somente leitura no PostgreSQL | a aplicação continuar tentando o endpoint errado sem alertar ninguém |
| Reconexão em massa pós-failover | retry com backoff e jitter | writer novo genuinamente subdimensionado — isso é o tier de promoção |
| Fatura sem controle de teto | alarme de ACU sustentada | a causa raiz da consulta ruim; o alarme avisa, não corrige |
- A curva de carga é medida por pelo menos uma semana antes de qualquer número ser fixado.
- O piso de ACU é derivado do conjunto de dados quente, para não esvaziar o buffer cache.
- O teto de ACU é derivado do pico medido, com margem declarada — não de um número redondo.
- O Terraform troca a classe fixa por `db.serverless` e declara a faixa no cluster.
- O reader entra em tier de promoção 0, para nascer do tamanho do writer.
- A aplicação usa dois pools: escrita no cluster endpoint, leitura no reader endpoint.
- Uma pipeline de retry com backoff e jitter cobre a reconexão pós-failover.
- O failover é disparado a mão, com um laço de escrita real medindo a janela.
- O tempo medido é comparado ao documentado — não presumido como igual.
- Um alarme de ACU sustentada perto do teto impede que o teto vire cheque em branco.
Perguntas frequentes
❓ O Aurora Serverless v2 escala em degraus, como trocar de classe de instância?
❓ Posso usar o endpoint de leitura do Aurora para gravar dados?
❓ Por que o failover do Aurora é mais rápido que o do RDS Multi-AZ clássico?
❓ Qual valor devo usar como ACU mínimo do Aurora Serverless v2?
❓ Uma transação longa trava a escala do Aurora Serverless v2?
❓ O reader do Aurora Serverless v2 escala junto com o writer?
❓ Aurora Serverless v2 pode escalar até zero ACU?
❓ Como faço o failover do meu cluster Aurora sem esperar uma falha real acontecer?
Fixando
Uma equipe configura o teto de ACU do cluster bem acima do necessário, "para não travar durante uma transação longa que o time roda todo fim de mês". O raciocínio é válido para o Aurora Serverless v2?
Um cluster Aurora com reader em tier de promoção 0 sofre a queda do writer. Você ensaiou o failover no laboratório e mediu cerca de 2 segundos de escritas rejeitadas. Por que o failover do Aurora tende a ser medido em segundos, e não nos minutos que recriar um banco do zero exigiria?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L14 no ar (cluster Aurora writer + reader, ambos servindo a aplicação do L07/L01), noções de PostgreSQL e Terraform |
| Conhecimentos adquiridos | como derivar piso e teto de ACU por medição; a diferença entre "scaling point" do v1 e o comportamento do v2; cluster endpoint vs. reader endpoint e o que acontece ao inverter os dois; por que o failover do Aurora é rápido, derivado da arquitetura de armazenamento compartilhado; tier de promoção e o que ele custa |
| Limitação que fica | um único reader é um único ponto de redundância — falha simultânea de duas AZs ainda derruba o cluster; e o alarme de ACU não distingue tráfego real de consulta ruim |
| Próximo exemplo recomendado | L16 — modelagem de tabela única no DynamoDB, que o L14 também deixou para depois |
| Também habilitado por este módulo | L58 (DR multi-região com Aurora Global) depende do failover ensaiado aqui como linha de base dentro de uma única região; L57 (chaos: derrubar uma AZ de propósito) usa este cluster como um dos alvos do experimento |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: How Aurora serverless works — arquitetura, unidade de capacidade (ACU) e o texto explícito sobre escala acontecer sem esperar um ponto de quietude; Performance and scaling for Aurora serverless — os fatores recomendados para escolher piso e teto; Amazon Aurora storage — o volume de cluster compartilhado entre writer e réplicas; High availability for Amazon Aurora — o mecanismo e o tempo típico de failover, e o comportamento dos tiers de promoção; e a página histórica de como o Aurora Serverless v1 funciona, consultada especificamente para confirmar que "scaling points" é um conceito daquela versão, e não do Serverless v2 usado neste laboratório. Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque ACU-hora varia por região e por opção de armazenamento (Standard vs. I/O-Optimized).
O que não foi verificado, e você deve conferir na sua conta
O tempo de failover de "cerca de 2 segundos" citado nas provas é o medido no ambiente de exemplo deste laboratório, sob a carga específica gerada pelo script — não um número garantido pela AWS. A documentação declara "tipicamente abaixo de 60 segundos, frequentemente abaixo de 30", e o seu resultado depende da sua carga, da sua configuração de driver e da sua lógica de retry. Da mesma forma, os valores de piso (2 ACU) e teto (32 ACU) foram derivados da curva medida na Cadência — meça a sua antes de copiar esses números.
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…