DevOps

RDS, ElastiCache e Estratégias de Dados na AWS Já leu

20 min de leitura

RDS, ElastiCache e Estratégias de Dados na AWS
Em qualquer sistema de produção, a camada de dados é simultaneamente a mais crítica e a mais difícil de recuperar quando algo dá errado. Um servidor de aplicação com problema pode ser substituído em segundos — um banco d

Em qualquer sistema de produção, a camada de dados é simultaneamente a mais crítica e a mais difícil de recuperar quando algo dá errado. Um servidor de aplicação com problema pode ser substituído em segundos — um banco de dados com dados corrompidos ou perdidos pode representar danos irreversíveis ao negócio.

A AWS oferece serviços gerenciados que transferem para a infraestrutura da nuvem as responsabilidades mais complexas do gerenciamento de dados: backups automáticos, replicação entre zonas de disponibilidade, failover automático, patching de segurança e scaling de storage. O time de engenharia mantém o controle sobre a configuração, os dados e o schema — a AWS cuida do hardware, do sistema operacional e do software de banco de dados.

Este artigo aprofunda o RDS para bancos de dados relacionais, o ElastiCache para caching distribuído e as estratégias de backup e recuperação de desastre que garantem durabilidade e disponibilidade dos dados em produção.


RDS em Alta Disponibilidade

Multi-AZ: Failover Automático Transparente

O modo Multi-AZ do RDS mantém uma réplica síncrona da instância primária em uma zona de disponibilidade diferente. Em caso de falha da instância primária — hardware, rede, zona de disponibilidade inteira — o RDS promove a réplica automaticamente em 60 a 120 segundos, sem intervenção manual e sem alteração no endpoint de conexão.

A réplica Multi-AZ não serve leituras em condições normais — é exclusivamente uma réplica de standby para failover. Para escalar leituras, são usadas Read Replicas.

# rds-producao.tf

resource "aws_db_instance" "principal" {
  identifier = "${var.project_name}-${var.environment}-primary"

  # Motor e versão
  engine               = "postgres"
  engine_version       = "16.1"
  instance_class       = "db.r6g.xlarge"

  # Storage com auto-scaling
  allocated_storage     = 100
  max_allocated_storage = 1000
  storage_type          = "gp3"
  storage_encrypted     = true
  kms_key_id            = aws_kms_key.rds.arn

  # Credenciais gerenciadas via Secrets Manager
  manage_master_user_password   = true
  master_username               = "postgres_admin"
  master_user_secret_kms_key_id = aws_kms_key.rds.arn

  # Alta disponibilidade
  multi_az               = true
  db_subnet_group_name   = aws_db_subnet_group.principal.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  publicly_accessible    = false

  # Parâmetros de banco
  db_name              = var.db_name
  parameter_group_name = aws_db_parameter_group.postgres16.name
  option_group_name    = aws_db_option_group.postgres16.name

  # Backups e manutenção
  backup_retention_period         = 14
  backup_window                   = "02:00-03:00"
  maintenance_window              = "Mon:03:00-Mon:04:00"
  auto_minor_version_upgrade      = true
  allow_major_version_upgrade     = false
  copy_tags_to_snapshot           = true
  deletion_protection             = true
  skip_final_snapshot             = false
  final_snapshot_identifier       = "${var.project_name}-${var.environment}-final"
  delete_automated_backups        = false

  # Monitoramento avançado
  monitoring_interval             = 60
  monitoring_role_arn             = aws_iam_role.rds_monitoring.arn
  performance_insights_enabled    = true
  performance_insights_retention_period = 7
  performance_insights_kms_key_id = aws_kms_key.rds.arn
  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]

  tags = local.tags_comuns
}

# Read Replica para leituras intensivas
resource "aws_db_instance" "replica_leitura" {
  identifier             = "${var.project_name}-${var.environment}-replica"
  replicate_source_db    = aws_db_instance.principal.identifier
  instance_class         = "db.r6g.large"
  publicly_accessible    = false
  vpc_security_group_ids = [aws_security_group.rds.id]
  parameter_group_name   = aws_db_parameter_group.postgres16.name

  # Réplicas não têm backups próprios — são recuperadas da primária
  backup_retention_period = 0
  skip_final_snapshot     = true
  deletion_protection     = false

  # Promoção automática em caso de falha da primária
  # (requer configuração adicional na aplicação)
  auto_minor_version_upgrade = true

  performance_insights_enabled = true

  tags = merge(local.tags_comuns, {
    Role = "read-replica"
  })
}

# Grupo de parâmetros otimizados para produção
resource "aws_db_parameter_group" "postgres16" {
  name   = "${var.project_name}-${var.environment}-pg16"
  family = "postgres16"

  # Configurações de performance
  parameter {
    name  = "shared_buffers"
    value = "{DBInstanceClassMemory/4096}"
  }

  parameter {
    name  = "effective_cache_size"
    value = "{DBInstanceClassMemory*3/4096}"
  }

  parameter {
    name  = "work_mem"
    value = "16384"  # 16MB por operação de sort
  }

  parameter {
    name  = "maintenance_work_mem"
    value = "524288"  # 512MB para operações de vacuum
  }

  # Configurações de logging
  parameter {
    name  = "log_min_duration_statement"
    value = "1000"  # loga queries > 1s
  }

  parameter {
    name  = "log_checkpoints"
    value = "1"
  }

  parameter {
    name  = "log_lock_waits"
    value = "1"
  }

  parameter {
    name  = "log_temp_files"
    value = "0"  # loga todos os arquivos temporários
  }

  # Configurações de conexão
  parameter {
    name  = "max_connections"
    value = "200"
  }

  # WAL e replicação
  parameter {
    name  = "wal_level"
    value = "replica"
    apply_method = "pending-reboot"
  }

  parameter {
    name  = "max_wal_senders"
    value = "5"
    apply_method = "pending-reboot"
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Chave KMS para criptografia do RDS
resource "aws_kms_key" "rds" {
  description             = "Chave KMS para criptografia do RDS"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  tags = local.tags_comuns
}

resource "aws_kms_alias" "rds" {
  name          = "alias/${var.project_name}-${var.environment}-rds"
  target_key_id = aws_kms_key.rds.key_id
}

RDS Proxy: Gerenciamento de Conexões para Aplicações de Alta Escala

Bancos de dados PostgreSQL têm um limite prático de conexões simultâneas — cada conexão consome memória significativa. Aplicações com muitas instâncias ou funções Lambda que abrem conexões novas a cada invocação podem rapidamente esgotar o pool de conexões disponíveis.

O RDS Proxy é um proxy gerenciado que mantém um pool de conexões persistentes ao banco e distribui as conexões das aplicações entre elas. Para funções Lambda especialmente, o Proxy é praticamente obrigatório em produção:

# RDS Proxy para gerenciamento de pool de conexões
resource "aws_db_proxy" "principal" {
  name                   = "${var.project_name}-${var.environment}-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         = module.vpc.ids_subnets_privadas

  auth {
    auth_scheme               = "SECRETS"
    description               = "Credenciais do banco de dados"
    iam_auth                  = "REQUIRED"
    secret_arn                = aws_db_instance.principal.master_user_secret[0].secret_arn
  }

  tags = local.tags_comuns
}

resource "aws_db_proxy_default_target_group" "principal" {
  db_proxy_name = aws_db_proxy.principal.name

  connection_pool_config {
    connection_borrow_timeout    = 120
    max_connections_percent      = 90
    max_idle_connections_percent = 50
  }
}

resource "aws_db_proxy_target" "principal" {
  db_instance_identifier = aws_db_instance.principal.identifier
  db_proxy_name          = aws_db_proxy.principal.name
  target_group_name      = aws_db_proxy_default_target_group.principal.name
}

ElastiCache: Caching Distribuído com Redis

O ElastiCache oferece Redis e Memcached como serviços gerenciados. Redis é a escolha dominante por sua versatilidade — suporta estruturas de dados complexas, pub/sub, streams, scripting Lua e persistência opcional.

Cluster Redis com Replicação

# elasticache.tf

# Subnet group para o ElastiCache
resource "aws_elasticache_subnet_group" "redis" {
  name       = "${var.project_name}-${var.environment}-redis-subnet"
  subnet_ids = module.vpc.ids_subnets_privadas

  tags = local.tags_comuns
}

# Grupo de parâmetros Redis
resource "aws_elasticache_parameter_group" "redis7" {
  name   = "${var.project_name}-${var.environment}-redis7"
  family = "redis7"

  # Habilita notificações de keyspace — útil para implementar
  # invalidação de cache orientada a eventos
  parameter {
    name  = "notify-keyspace-events"
    value = "Ex"  # E=keyspace events, x=expired events
  }

  # Desabilita comandos perigosos em produção
  parameter {
    name  = "rename-commands"
    value = "FLUSHALL banned FLUSHDB banned CONFIG banned"
  }

  tags = local.tags_comuns
}

# Cluster Redis com replicação e failover automático
resource "aws_elasticache_replication_group" "redis" {
  replication_group_id = "${var.project_name}-${var.environment}-redis"
  description          = "Cluster Redis para ${var.project_name} ${var.environment}"

  node_type            = var.redis_node_type
  num_cache_clusters   = var.environment == "production" ? 3 : 1
  port                 = 6379
  parameter_group_name = aws_elasticache_parameter_group.redis7.name
  subnet_group_name    = aws_elasticache_subnet_group.redis.name
  security_group_ids   = [aws_security_group.redis.id]

  # Alta disponibilidade
  automatic_failover_enabled  = var.environment == "production"
  multi_az_enabled            = var.environment == "production"
  at_rest_encryption_enabled  = true
  transit_encryption_enabled  = true
  auth_token                  = var.redis_auth_token
  auth_token_update_strategy  = "ROTATE"

  # Versão Redis
  engine_version = "7.1"

  # Manutenção e backups
  maintenance_window         = "tue:03:00-tue:04:00"
  snapshot_window            = "02:00-03:00"
  snapshot_retention_limit   = 7
  auto_minor_version_upgrade = true

  # Logs para CloudWatch
  log_delivery_configuration {
    destination      = aws_cloudwatch_log_group.redis_slow.name
    destination_type = "cloudwatch-logs"
    log_format       = "json"
    log_type         = "slow-log"
  }

  log_delivery_configuration {
    destination      = aws_cloudwatch_log_group.redis_engine.name
    destination_type = "cloudwatch-logs"
    log_format       = "json"
    log_type         = "engine-log"
  }

  tags = local.tags_comuns
}

resource "aws_cloudwatch_log_group" "redis_slow" {
  name              = "/elasticache/${var.project_name}-${var.environment}/slow-log"
  retention_in_days = 14
}

resource "aws_cloudwatch_log_group" "redis_engine" {
  name              = "/elasticache/${var.project_name}-${var.environment}/engine-log"
  retention_in_days = 7
}

Padrões de Caching na Aplicação

Com a infraestrutura do Redis provisionada, a aplicação implementa os padrões de caching que reduzem a carga no banco de dados:

// src/cache/redis.client.js
const { createClient } = require('redis');

let client = null;

async function getClient() {
  if (client?.isReady) return client;

  client = createClient({
    url: process.env.REDIS_URL,
    socket: {
      tls: true,
      rejectUnauthorized: true,
      connectTimeout: 5000,
    },
    password: process.env.REDIS_AUTH_TOKEN,
  });

  client.on('error', (err) => {
    console.error(JSON.stringify({ level: 'error', msg: 'Redis error', err: err.message }));
  });

  client.on('reconnecting', () => {
    console.warn(JSON.stringify({ level: 'warn', msg: 'Redis reconnecting' }));
  });

  await client.connect();
  return client;
}

module.exports = { getClient };
// src/cache/cache.service.js
const { getClient } = require('./redis.client');

const PREFIXO = process.env.SERVICE_NAME || 'api';
const TTL_PADRAO = 300; // 5 minutos

class CacheService {
  // ── Cache-Aside (Lazy Loading) ────────────────────
  // Busca no cache; se não encontrar, busca na fonte e armazena
  async getOuBuscar(chave, funcaoBusca, ttl = TTL_PADRAO) {
    const redis = await getClient();
    const chaveCompleta = `${PREFIXO}:${chave}`;

    // Tenta buscar no cache
    const cached = await redis.get(chaveCompleta);
    if (cached) {
      return JSON.parse(cached);
    }

    // Cache miss — busca na fonte de dados
    const dados = await funcaoBusca();

    // Armazena no cache para próximas requisições
    await redis.setEx(
      chaveCompleta,
      ttl,
      JSON.stringify(dados)
    );

    return dados;
  }

  // ── Invalidação por chave específica ──────────────
  async invalidar(chave) {
    const redis = await getClient();
    await redis.del(`${PREFIXO}:${chave}`);
  }

  // ── Invalidação por padrão ────────────────────────
  // Útil para invalidar todos os caches relacionados a uma entidade
  async invalidarPadrao(padrao) {
    const redis = await getClient();
    const chaves = await redis.keys(`${PREFIXO}:${padrao}`);

    if (chaves.length > 0) {
      // Usa pipeline para deletar múltiplas chaves atomicamente
      const pipeline = redis.multi();
      chaves.forEach(chave => pipeline.del(chave));
      await pipeline.exec();
    }

    return chaves.length;
  }

  // ── Write-Through Cache ───────────────────────────
  // Atualiza cache e banco de dados simultaneamente
  async escreverComCache(chave, dados, funcaoEscrita, ttl = TTL_PADRAO) {
    const redis = await getClient();
    const chaveCompleta = `${PREFIXO}:${chave}`;

    // Executa as operações em paralelo
    await Promise.all([
      funcaoEscrita(dados),
      redis.setEx(chaveCompleta, ttl, JSON.stringify(dados))
    ]);

    return dados;
  }

  // ── Rate Limiting com Redis ───────────────────────
  async verificarRateLimit(identificador, limite, janelaSegundos) {
    const redis = await getClient();
    const chave = `${PREFIXO}:rate:${identificador}`;

    // Script Lua garante atomicidade
    const script = `
      local atual = redis.call('INCR', KEYS[1])
      if atual == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[2])
      end
      return { atual, redis.call('TTL', KEYS[1]) }
    `;

    const [contagem, ttl] = await redis.eval(
      script,
      { keys: [chave], arguments: [limite.toString(), janelaSegundos.toString()] }
    );

    return {
      permitido: contagem <= limite,
      contagem: parseInt(contagem),
      limite,
      resetEm: parseInt(ttl),
    };
  }

  // ── Distributed Lock ─────────────────────────────
  // Evita processamento duplicado em sistemas distribuídos
  async adquirirLock(recurso, ttlMs = 30000) {
    const redis = await getClient();
    const chave = `${PREFIXO}:lock:${recurso}`;
    const token = `${Date.now()}-${Math.random()}`;

    // SET NX (only if Not eXists) com expiração
    const adquirido = await redis.set(chave, token, {
      NX: true,
      PX: ttlMs,
    });

    if (!adquirido) {
      return null;  // Lock não adquirido
    }

    // Retorna função para liberar o lock
    return async () => {
      // Script Lua garante que apenas quem adquiriu pode liberar
      const scriptLiberar = `
        if redis.call('GET', KEYS[1]) == ARGV[1] then
          return redis.call('DEL', KEYS[1])
        else
          return 0
        end
      `;
      await redis.eval(scriptLiberar, { keys: [chave], arguments: [token] });
    };
  }
}

module.exports = new CacheService();

Uso do serviço de cache na camada de repositório:

// src/repositories/produto.repository.js
const cache = require('../cache/cache.service');
const db = require('../database/db');

class ProdutoRepository {
  async buscarPorId(id) {
    return cache.getOuBuscar(
      `produto:${id}`,
      () => db.query('SELECT * FROM produtos WHERE id = $1', [id])
        .then(result => result.rows[0]),
      600  // 10 minutos
    );
  }

  async buscarCategoria(categoriaId) {
    return cache.getOuBuscar(
      `categoria:${categoriaId}:produtos`,
      () => db.query(
        'SELECT * FROM produtos WHERE categoria_id = $1 AND ativo = true ORDER BY nome',
        [categoriaId]
      ).then(result => result.rows),
      300  // 5 minutos
    );
  }

  async atualizar(id, dados) {
    const produto = await db.query(
      'UPDATE produtos SET nome = $1, preco = $2, updated_at = NOW() WHERE id = $3 RETURNING *',
      [dados.nome, dados.preco, id]
    ).then(result => result.rows[0]);

    // Invalida o cache do produto e da categoria
    await Promise.all([
      cache.invalidar(`produto:${id}`),
      cache.invalidarPadrao(`categoria:${produto.categoria_id}:*`),
    ]);

    return produto;
  }
}

module.exports = new ProdutoRepository();

Estratégias de Backup e Recuperação de Desastre

Os Objetivos de Recuperação: RPO e RTO

Toda estratégia de dados começa com dois números que definem o que é aceitável perder e quanto tempo de indisponibilidade é tolerável:

RPO — Recovery Point Objective — o máximo de dados que pode ser perdido em caso de desastre. Se o RPO é de 1 hora, a organização aceita perder até 1 hora de transações. Se o RPO é de zero, nenhuma transação pode ser perdida — o que exige replicação síncrona.

RTO — Recovery Time Objective — o máximo de tempo que o sistema pode ficar indisponível antes de ser restaurado. Se o RTO é de 15 minutos, o sistema deve estar operacional em no máximo 15 minutos após uma falha.

RPO e RTO mais agressivos custam mais — em infraestrutura, em complexidade operacional e em custos de engenharia. A definição desses objetivos é uma decisão de negócio, não apenas técnica.

Hierarquia de Backup do RDS

O RDS oferece três níveis de proteção de dados que trabalham em camadas:

Backups automáticos — o RDS faz snapshots diários do volume de storage e retém os logs de transação (WAL) para permitir recuperação point-in-time. A janela de retenção é configurável de 1 a 35 dias. Permitem restaurar para qualquer segundo dentro da janela de retenção.

Snapshots manuais — criados explicitamente e retidos indefinidamente até serem deletados manualmente. Adequados para pontos de checkpoint antes de operações arriscadas — migrations, atualizações de versão, mudanças de schema.

Backups cross-region — réplicas de snapshots em outra região para proteção contra falha regional completa. Essencial para RPO baixo em cenários de disaster recovery.

# Operações de backup e restauração via AWS CLI

# Cria snapshot manual antes de uma migration
aws rds create-db-snapshot \
  --db-instance-identifier minha-api-production-db \
  --db-snapshot-identifier pre-migration-$(date +%Y%m%d-%H%M%S) \
  --tags Key=Motivo,Value=pre-migration Key=Autor,Value=joao

# Lista snapshots disponíveis
aws rds describe-db-snapshots \
  --db-instance-identifier minha-api-production-db \
  --query 'DBSnapshots[*].{ID:DBSnapshotIdentifier,Status:Status,Criado:SnapshotCreateTime}' \
  --output table

# Restaura para um ponto específico no tempo (point-in-time recovery)
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier minha-api-production-db \
  --target-db-instance-identifier minha-api-recovery-test \
  --restore-time 2025-03-10T14:30:00Z \
  --db-instance-class db.r6g.large \
  --no-multi-az \
  --db-subnet-group-name minha-api-staging-db-subnet-group \
  --vpc-security-group-ids sg-0abc123

# Copia snapshot para outra região (cross-region backup)
aws rds copy-db-snapshot \
  --source-db-snapshot-identifier \
    arn:aws:rds:us-east-1:123456789:snapshot:pre-migration-20250310 \
  --target-db-snapshot-identifier pre-migration-20250310-dr \
  --source-region us-east-1 \
  --region us-west-2 \
  --kms-key-id arn:aws:kms:us-west-2:123456789:key/abc-def

Automatizando Backups com AWS Backup

O AWS Backup é um serviço centralizado que gerencia backups de múltiplos serviços AWS — RDS, EBS, EFS, DynamoDB, S3 — com uma única política:

# aws-backup.tf

resource "aws_backup_vault" "principal" {
  name        = "${var.project_name}-${var.environment}-vault"
  kms_key_arn = aws_kms_key.backup.arn

  tags = local.tags_comuns
}

# Vault de destino para cross-region replication
resource "aws_backup_vault" "dr_region" {
  provider    = aws.dr_region
  name        = "${var.project_name}-${var.environment}-vault-dr"
  kms_key_arn = aws_kms_key.backup_dr.arn
}

resource "aws_backup_plan" "producao" {
  name = "${var.project_name}-${var.environment}-backup-plan"

  rule {
    rule_name         = "backup-diario"
    target_vault_name = aws_backup_vault.principal.name
    schedule          = "cron(0 2 * * ? *)"  # Todo dia às 2h UTC

    start_window_minutes      = 60
    completion_window_minutes = 180
    recovery_point_tags       = local.tags_comuns

    lifecycle {
      cold_storage_after = 30   # Move para Glacier após 30 dias
      delete_after       = 90   # Deleta após 90 dias
    }

    # Replica backup para outra região
    copy_action {
      destination_vault_arn = aws_backup_vault.dr_region.arn

      lifecycle {
        delete_after = 30  # Mantém DR por 30 dias
      }
    }
  }

  rule {
    rule_name         = "backup-semanal"
    target_vault_name = aws_backup_vault.principal.name
    schedule          = "cron(0 3 ? * SUN *)"  # Todo domingo às 3h UTC

    lifecycle {
      delete_after = 365  # Retém backups semanais por 1 ano
    }
  }

  tags = local.tags_comuns
}

# Associa o plano de backup ao RDS e outros recursos
resource "aws_backup_selection" "banco_dados" {
  name         = "banco-dados-producao"
  iam_role_arn = aws_iam_role.backup.arn
  plan_id      = aws_backup_plan.producao.id

  resources = [
    aws_db_instance.principal.arn,
    aws_elasticache_replication_group.redis.arn,
  ]

  condition {
    string_equals {
      key   = "aws:ResourceTag/Environment"
      value = "production"
    }
  }
}

Testando a Recuperação

Uma estratégia de backup sem testes regulares de recuperação é uma falsa segurança. O processo de testar a recuperação deve ser automatizado e executado regularmente:

#!/bin/bash
# scripts/testar-recuperacao.sh
# Testa a capacidade de recuperação do banco de dados a partir de um snapshot
# Deve ser executado mensalmente via pipeline de CI/CD

set -euo pipefail

SNAPSHOT_ID="$1"
AMBIENTE_TESTE="recovery-test-$(date +%Y%m%d)"
REGIAO="us-east-1"

log() { echo "[$(date -u +%H:%M:%S)] $*"; }
erro() { echo "[$(date -u +%H:%M:%S)] ERRO: $*" >&2; exit 1; }

log "=== Iniciando teste de recuperação ==="
log "Snapshot: $SNAPSHOT_ID"
log "Ambiente de teste: $AMBIENTE_TESTE"

# Restaura o banco de dados a partir do snapshot
log "Restaurando banco de dados..."
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier "$AMBIENTE_TESTE" \
  --db-snapshot-identifier "$SNAPSHOT_ID" \
  --db-instance-class "db.t3.micro" \
  --no-multi-az \
  --no-publicly-accessible \
  --db-subnet-group-name "minha-api-staging-db-subnet-group" \
  --vpc-security-group-ids "sg-0abc123" \
  --region "$REGIAO"

# Aguarda o banco ficar disponível
log "Aguardando banco ficar disponível..."
aws rds wait db-instance-available \
  --db-instance-identifier "$AMBIENTE_TESTE" \
  --region "$REGIAO"

log "Banco disponível. Executando verificações..."

# Obtém o endpoint do banco restaurado
ENDPOINT=$(aws rds describe-db-instances \
  --db-instance-identifier "$AMBIENTE_TESTE" \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text \
  --region "$REGIAO")

# Executa verificações de integridade
RESULTADO=$(psql \
  "postgresql://postgres_admin:$DB_PASSWORD@$ENDPOINT:5432/producao" \
  -c "SELECT COUNT(*) FROM usuarios;" \
  -t -A)

if [ -z "$RESULTADO" ] || [ "$RESULTADO" -eq 0 ]; then
  erro "Verificação falhou: tabela usuarios vazia ou inacessível"
fi

log "Verificação bem-sucedida: $RESULTADO usuários encontrados"

# Verifica integridade referencial
psql "postgresql://postgres_admin:$DB_PASSWORD@$ENDPOINT:5432/producao" \
  -c "SELECT COUNT(*) FROM pedidos p LEFT JOIN usuarios u ON p.usuario_id = u.id WHERE u.id IS NULL;" \
  -t -A | {
    read ORFAOS
    if [ "$ORFAOS" -gt 0 ]; then
      log "AVISO: $ORFAOS pedidos órfãos encontrados — possível problema de integridade"
    else
      log "Integridade referencial: OK"
    fi
  }

# Registra o resultado do teste
aws cloudwatch put-metric-data \
  --namespace "BackupTests" \
  --metric-name "RecoveryTestSuccess" \
  --value 1 \
  --dimensions "Environment=production" \
  --region "$REGIAO"

log "=== Teste de recuperação concluído com sucesso ==="

# Limpa o banco de teste
log "Removendo banco de teste..."
aws rds delete-db-instance \
  --db-instance-identifier "$AMBIENTE_TESTE" \
  --skip-final-snapshot \
  --region "$REGIAO"

log "Banco de teste removido."

Monitoramento da Camada de Dados

Alertas Essenciais para RDS e ElastiCache

# observabilidade/prometheus/rules/alertas-dados.yml
groups:
  - name: rds
    rules:
      - alert: RDSAltaCPU
        expr: aws_rds_cpuutilization_average > 80
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "CPU do RDS acima de 80%"
          description: |
            Uso de CPU: {{ $value }}%
            Considerar: otimização de queries, upgrade de instância
            ou adicionar read replica para distribuir carga.

      - alert: RDSConexoesProximasDoLimite
        expr: |
          aws_rds_database_connections_average
          /
          aws_rds_database_connections_average offset 0s * 0 + 200
          > 0.85
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Conexões RDS acima de 85% do máximo"
          runbook_url: "https://wiki.empresa.com/runbooks/conexoes-rds"

      - alert: RDSStorageAbaixo20Percent
        expr: |
          aws_rds_free_storage_space_average
          /
          aws_rds_allocated_storage_average
          < 0.20
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "Storage do RDS com menos de 20% livre"
          description: "Storage livre: {{ $value | humanizePercentage }}"

  - name: elasticache
    rules:
      - alert: RedisBaixaMemoriaDisponivel
        expr: |
          aws_elasticache_freeable_memory_average
          /
          aws_elasticache_bytes_used_for_cache_average
          < 0.10
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Redis com menos de 10% de memória livre"
          description: |
            O Redis está próximo da capacidade máxima.
            Risco de eviction de chaves e degradação de performance.

      - alert: RedisCacheHitRateBaixa
        expr: |
          aws_elasticache_cache_hits_average
          /
          (aws_elasticache_cache_hits_average + aws_elasticache_cache_misses_average)
          < 0.80
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Cache hit rate do Redis abaixo de 80%"
          description: |
            Cache hit rate atual: {{ $value | humanizePercentage }}
            Hit rate baixo aumenta a carga no banco de dados.

O Que Vem a Seguir

O próximo artigo completa o que vimos com os serviços de rede e entrega de conteúdo da AWS — Route53 para DNS, CloudFront para CDN e ACM para certificados SSL/TLS — os componentes que ficam entre os usuários e a aplicação.


Referências para Aprofundamento

Documentação oficial AWS - Amazon RDS Documentation — docs.aws.amazon.com — Documentação completa do RDS, incluindo guias de Multi-AZ, Read Replicas, RDS Proxy e boas práticas de performance. - Amazon ElastiCache for Redis — docs.aws.amazon.com — Documentação do ElastiCache para Redis, cobrindo clustering, replicação, configuração de parâmetros e monitoramento. - AWS Backup Documentation — docs.aws.amazon.com — Guia completo do AWS Backup, incluindo planos de backup, vaults e replicação cross-region.

Performance e otimização - RDS Performance Insights — docs.aws.amazon.com — Documentação do Performance Insights com guia de interpretação dos dados e identificação de gargalos. - Redis Best Practices — redis.io — Documentação oficial do Redis com padrões de uso, incluindo caching, rate limiting, pub/sub e distributed locks.

Disaster Recovery - AWS Disaster Recovery Whitepaper — docs.aws.amazon.com — Whitepaper oficial da AWS sobre estratégias de disaster recovery, definindo os padrões Backup & Restore, Pilot Light, Warm Standby e Multi-Site Active/Active.

Comentários

Mais em DevOps

Deploy Automático para Servidores com GitHub Actions
Deploy Automático para Servidores com GitHub Actions

Todo o trabalho construído nos artigos anteriores — testes, build de imagens,...

SSH: Conectando e Gerenciando Servidores Remotos
SSH: Conectando e Gerenciando Servidores Remotos

SSH &mdash;&nbsp;Secure Shell &mdash; &eacute; o protocolo pelo qual administ...

Compute, Storage e Redes no Azure
Compute, Storage e Redes no Azure

O artigo anterior estabeleceu a visão geral: como o Azure se organiza, como a...