RDS PostgreSQL na AWS: Setup, Segurança e Monitoramento
RDS PostgreSQL é o banco de dados relacional mais usado na AWS. Bem configurado, você tem backups automáticos, failover, patching e monitoramento gerenciados. Mal configurado, você tem um banco caro, lento e vulnerável.
Escolhendo a classe de instância
db.t3.micro — dev/teste, 2 vCPU, 1GB RAM. Burst CPU (não use em produção)
db.t3.medium — pequenos apps, 2 vCPU, 4GB RAM. Burst CPU
db.m6g.large — produção pequena, 2 vCPU, 8GB RAM. ARM64, 15% mais barato que m5
db.m6g.xlarge — produção média, 4 vCPU, 16GB RAM
db.r6g.large — read-heavy, 2 vCPU, 16GB RAM (otimizado para memória)Regra prática: use db.t3 apenas para ambientes não-produtivos. Em produção, comece com db.m6g.large e escale verticalmente baseado em métricas reais.
Terraform: provisionamento completo
# rds.tf
resource "aws_db_subnet_group" "main" {
name = "${var.project}-rds-subnet-group"
subnet_ids = var.private_subnet_ids # NUNCA em subnet pública
tags = { Name = "${var.project}-rds-subnet-group" }
}
resource "aws_security_group" "rds" {
name = "${var.project}-rds-sg"
vpc_id = var.vpc_id
# Só aceita conexões de dentro da VPC (app servers)
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_db_parameter_group" "postgres16" {
name = "${var.project}-postgres16"
family = "postgres16"
# Logging de queries lentas (> 1 segundo)
parameter {
name = "log_min_duration_statement"
value = "1000"
}
# Performance
parameter {
name = "shared_buffers"
value = "{DBInstanceClassMemory/4}" # 25% da RAM
}
parameter {
name = "work_mem"
value = "16384" # 16MB por sort/hash operation
}
parameter {
name = "effective_cache_size"
value = "{DBInstanceClassMemory*3/4}" # 75% da RAM
}
# Durabilidade vs Performance
parameter {
name = "synchronous_commit"
value = "on" # Nunca desative em produção
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project}-postgres"
engine = "postgres"
engine_version = "16.3"
instance_class = var.db_instance_class
allocated_storage = 100
storage_type = "gp3"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
db_name = var.db_name
username = "dbadmin"
password = random_password.db_master.result
# Alta disponibilidade
multi_az = true # Replica síncrona em outra AZ
publicly_accessible = false # NUNCA true em produção
# Backup
backup_retention_period = 30 # 30 dias de backup
backup_window = "03:00-04:00" # UTC (meia-noite BRT)
maintenance_window = "Mon:04:00-Mon:05:00"
# Configuração
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
parameter_group_name = aws_db_parameter_group.postgres16.name
# Proteção contra deleção acidental
deletion_protection = true
skip_final_snapshot = false
final_snapshot_identifier = "${var.project}-final-snapshot"
# IAM authentication (dispensa senha na app)
iam_database_authentication_enabled = true
tags = { Name = "${var.project}-postgres" }
}Multi-AZ: o que realmente protege
Multi-AZ cria uma réplica síncrona em outra AZ. Em caso de falha da instância primária, o RDS faz failover automático em 60-120 segundos.
O que Multi-AZ protege:
- Falha de hardware na AZ primária
- Manutenção programada (failover durante patching)
- Falha de rede na AZ primária
O que Multi-AZ NÃO protege:
- Corrupção de dados (replicada instantaneamente para a standby)
- Erro humano (DELETE sem WHERE — precisa de backup point-in-time)
- Falha regional (precisa de Cross-Region Read Replica)
IAM Database Authentication
Elimina senhas hardcoded na aplicação. A app usa um token temporário gerado via SDK:
// lib/db.js
import { Signer } from '@aws-sdk/rds-signer';
import pg from 'pg';
const signer = new Signer({
hostname: process.env.RDS_HOSTNAME,
port: 5432,
region: 'us-east-1',
username: 'app_user',
});
export async function criarConexao() {
const token = await signer.getAuthToken(); // válido por 15 minutos
return new pg.Pool({
host: process.env.RDS_HOSTNAME,
database: process.env.DB_NAME,
user: 'app_user',
password: token,
ssl: { rejectUnauthorized: true, ca: process.env.RDS_CA },
max: 10,
});
}A role IAM da aplicação precisa de permissão rds-db:connect:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:us-east-1:123456789:dbuser:*/app_user"
}]
}Point-in-Time Recovery
# Restore para um ponto específico via AWS CLI
aws rds restore-db-instance-to-point-in-time \
--source-db-instance-identifier meu-projeto-postgres \
--target-db-instance-identifier meu-projeto-postgres-restored \
--restore-time "2024-11-15T14:30:00Z"Com 30 dias de backup, você pode restaurar para qualquer segundo dentro desse período — essencial após DELETE/UPDATE acidental.
CloudWatch: métricas para monitorar
CPUUtilization > 80% por 5min → alertar
FreeableMemory < 500MB → alertar
DatabaseConnections > 80% do max → alertar (max = max_connections no param group)
ReadLatency > 20ms → investigar
WriteLatency > 20ms → investigar
FreeStorageSpace < 20GB → alertar (auto-grow ou aumentar alocação)
ReplicaLag > 1s → alertar (se tiver read replica)Connection Pooling com RDS Proxy
Lambdas e containers criam e destroem conexões frequentemente. Sem pooler, você esgota o max_connections do PostgreSQL:
resource "aws_db_proxy" "main" {
name = "${var.project}-proxy"
debug_logging = false
engine_family = "POSTGRESQL"
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.rds_proxy.arn
vpc_security_group_ids = [aws_security_group.rds_proxy.id]
vpc_subnet_ids = var.private_subnet_ids
auth {
auth_scheme = "SECRETS"
secret_arn = aws_secretsmanager_secret.db_password.arn
iam_auth = "REQUIRED"
}
target_group {
db_instance_identifier = aws_db_instance.main.identifier
connection_pool_config {
max_connections_percent = 90
max_idle_connections_percent = 50
}
}
}A aplicação conecta no endpoint do Proxy em vez do RDS direto. O Proxy reutiliza conexões entre invocações de Lambda, reduzindo latência e evitando too many connections.
Checklist de segurança
-
publicly_accessible = false— sem acesso direto da internet - Subnet privada — sem rotas para internet gateway
- Security group só permite porta 5432 dos app servers
-
storage_encrypted = true— dados em repouso cifrados - SSL obrigatório na conexão
- IAM auth em vez de senha fixa
-
deletion_protection = true - Backup mínimo 7 dias (30 para produção)