Ir para o conteúdo
Ferramenta para Plataforma1701 palavras

MongoDB para AWS: Guia Completo de Deploy

Introdução

Trabalhar com MongoDB na AWS é uma combinação que aparece em praticamente todo projeto moderno que exige flexibilidade de modelagem e escala horizontal. Nos últimos anos, configurei e mantive clusters MongoDB em produção na AWS para aplicações que vão desde marketplaces com milhões de documentos até plataformas de IoT ingerindo dados em tempo real. A experiência me ensinou que a diferença entre um setup que funciona e um que sobrevive em produção está nos detalhes de infraestrutura.

Este guia cobre tudo o que você precisa saber para rodar MongoDB na AWS com confiança: desde a escolha entre self-managed e DocumentDB, passando por configuração de rede e segurança, até exemplos práticos de código em TypeScript para interagir com seu banco. O conteúdo é voltado para desenvolvedores com pelo menos dois anos de experiência que querem entender não só o "como", mas o "por quê" de cada decisão.

Se você está começando sua jornada em infraestrutura cloud, recomendo antes a leitura do post DevOps para Devs: Docker, CI/CD, Kubernetes e AWS em Produção para ter uma base sólida.

Por que usar MongoDB com AWS

Flexibilidade de modelagem encontra elasticidade de infraestrutura

MongoDB é um banco orientado a documentos que permite esquemas flexíveis, o que é ideal para domínios onde os requisitos mudam com frequência. A AWS oferece a infraestrutura elástica que complementa essa flexibilidade: você pode escalar compute e storage de forma independente, distribuir réplicas entre Availability Zones e integrar com dezenas de serviços gerenciados.

Casos de uso onde essa combinação brilha

Alguns cenários onde vi MongoDB na AWS entregar resultados excepcionais:

  • Catálogos de e-commerce: produtos com atributos variáveis (roupas têm tamanho, eletrônicos têm voltagem) se encaixam perfeitamente no modelo de documentos. Combinado com ElastiCache na frente, a performance é excelente.
  • Plataformas de IoT: ingestão massiva de dados de sensores com TTL indexes para expiração automática, rodando em instâncias otimizadas para I/O na AWS.
  • Aplicações multi-tenant SaaS: cada tenant pode ter configurações diferentes armazenadas como documentos, sem migrações de schema a cada mudança.
  • Sistemas de gerenciamento de conteúdo: CMSs headless onde a estrutura do conteúdo é definida pelo usuário em tempo de execução.

DocumentDB vs MongoDB Atlas vs Self-Managed no EC2

Antes de qualquer configuração, você precisa tomar uma decisão arquitetural:

Amazon DocumentDB é um serviço gerenciado compatível com a API do MongoDB (até a versão 5.0). Ele simplifica operações, mas não é MongoDB real. Algumas funcionalidades como change streams têm comportamento diferente, e aggregation pipelines complexas podem falhar silenciosamente.

MongoDB Atlas na AWS roda MongoDB legítimo em infraestrutura AWS, gerenciado pela própria MongoDB Inc. Oferece a experiência mais completa, incluindo Atlas Search, Atlas Triggers e backups contínuos com point-in-time recovery.

Self-Managed no EC2 dá controle total, mas exige que você gerencie replica sets, backups, patches e monitoramento. Faça isso apenas se houver requisitos regulatórios que impeçam o uso de serviços gerenciados.

Para a maioria dos cenários, minha recomendação é MongoDB Atlas. Para este guia, vou cobrir tanto o setup via Atlas com peering na AWS quanto o provisionamento com DocumentDB via CLI.

Setup do MongoDB na AWS

Provisionando Amazon DocumentDB via CLI

Primeiro, crie o cluster de subnet group e o próprio cluster via AWS CLI:

Bash
# Criar subnet group para o DocumentDB
aws docdb create-db-subnet-group \
  --db-subnet-group-name mongodb-subnet-group \
  --db-subnet-group-description "Subnets privadas para DocumentDB" \
  --subnet-ids subnet-0a1b2c3d4e5f00001 subnet-0a1b2c3d4e5f00002
 
# Criar security group dedicado
aws ec2 create-security-group \
  --group-name docdb-sg \
  --description "Security group para DocumentDB" \
  --vpc-id vpc-0a1b2c3d4e5f00000
 
# Liberar porta 27017 apenas para o CIDR da VPC
aws ec2 authorize-security-group-ingress \
  --group-name docdb-sg \
  --protocol tcp \
  --port 27017 \
  --cidr 10.0.0.0/16
 
# Criar o cluster DocumentDB
aws docdb create-db-cluster \
  --db-cluster-identifier producao-docdb \
  --engine docdb \
  --engine-version 5.0.0 \
  --master-username adminprincipal \
  --master-user-password "SenhaSegura2024!Prod" \
  --vpc-security-group-ids sg-0a1b2c3d4e5f00000 \
  --db-subnet-group-name mongodb-subnet-group \
  --storage-encrypted \
  --backup-retention-period 7
 
# Adicionar instâncias ao cluster (mínimo 2 para alta disponibilidade)
aws docdb create-db-instance \
  --db-instance-identifier producao-docdb-instance-1 \
  --db-cluster-identifier producao-docdb \
  --db-instance-class db.r6g.large \
  --engine docdb
 
aws docdb create-db-instance \
  --db-instance-identifier producao-docdb-instance-2 \
  --db-cluster-identifier producao-docdb \
  --db-instance-class db.r6g.large \
  --engine docdb

Configurando o Parameter Group

O parameter group permite ajustar configurações como o profiler e o TTL monitor:

Bash
aws docdb create-db-cluster-parameter-group \
  --db-cluster-parameter-group-name producao-params \
  --db-parameter-group-family docdb5.0 \
  --description "Parametros otimizados para producao"
 
aws docdb modify-db-cluster-parameter-group \
  --db-cluster-parameter-group-name producao-params \
  --parameters "ParameterName=profiler,ParameterValue=enabled,ApplyMethod=immediate" \
  --parameters "ParameterName=profiler_threshold_ms,ParameterValue=100,ApplyMethod=immediate"

Armazenando credenciais no AWS Secrets Manager

Nunca coloque credenciais no código ou em variáveis de ambiente de arquivos .env. Use o Secrets Manager:

Bash
aws secretsmanager create-secret \
  --name producao/docdb/credentials \
  --description "Credenciais do DocumentDB de producao" \
  --secret-string '{"username":"adminprincipal","password":"SenhaSegura2024!Prod","host":"producao-docdb.cluster-abc123.us-east-1.docdb.amazonaws.com","port":27017}'

Exemplo prático com TypeScript

Conexão segura e operações CRUD

O primeiro exemplo mostra como estabelecer uma conexão segura com o DocumentDB (que exige TLS) e realizar operações básicas. Note o download do certificado CA da AWS, que é obrigatório:

TYPESCRIPT
import { MongoClient, type Db, type Collection, type Document } from "mongodb";
import {
  SecretsManagerClient,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
import { readFileSync } from "fs";
import { join } from "path";
 
interface DbCredentials {
  username: string;
  password: string;
  host: string;
  port: number;
}
 
interface Product {
  name: string;
  category: string;
  price: number;
  attributes: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}
 
async function getCredentials(): Promise<DbCredentials> {
  const client = new SecretsManagerClient({ region: "us-east-1" });
  const command = new GetSecretValueCommand({
    SecretId: "producao/docdb/credentials",
  });
  const response = await client.send(command);
 
  if (!response.SecretString) {
    throw new Error("Credenciais nao encontradas no Secrets Manager");
  }
 
  return JSON.parse(response.SecretString) as DbCredentials;
}
 
async function createConnection(): Promise<MongoClient> {
  const creds = await getCredentials();
 
  // Certificado CA obrigatório para DocumentDB com TLS
  // Baixe com: wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
  const caFilePath = join(__dirname, "global-bundle.pem");
  const ca = readFileSync(caFilePath, "utf-8");
 
  const uri =
    `mongodb://${creds.username}:${encodeURIComponent(creds.password)}` +
    `@${creds.host}:${creds.port}/?tls=true&replicaSet=rs0&readPreference=secondaryPreferred`;
 
  const client = new MongoClient(uri, {
    tls: true,
    tlsCAFile: caFilePath,
    retryWrites: false, // DocumentDB nao suporta retryWrites
    maxPoolSize: 50,
    minPoolSize: 5,
    maxIdleTimeMS: 60000,
    connectTimeoutMS: 10000,
    serverSelectionTimeoutMS: 10000,
  });
 
  await client.connect();
  console.log("Conexao com DocumentDB estabelecida com sucesso");
 
  return client;
}
 
async function productOperations(): Promise<void> {
  const client = await createConnection();
 
  try {
    const db: Db = client.db("ecommerce");
    const products: Collection<Product> = db.collection("products");
 
    // Criar indices para queries frequentes
    await products.createIndex({ category: 1, price: -1 });
    await products.createIndex({ name: "text" });
    await products.createIndex(
      { createdAt: 1 },
      { expireAfterSeconds: 86400 * 365 } // TTL de 1 ano
    );
 
    // Inserir produto com atributos flexiveis
    const newProduct: Product = {
      name: "Notebook Pro X1",
      category: "eletronicos",
      price: 4599.9,
      attributes: {
        brand: "TechBrand",
        ram: "16GB",
        storage: "512GB SSD",
        processor: "Intel i7 13th Gen",
        weight: 1.4,
      },
      createdAt: new Date(),
      updatedAt: new Date(),
    };
 
    const insertResult = await products.insertOne(newProduct);
    console.log(`Produto inserido com ID: ${insertResult.insertedId}`);
 
    // Buscar produtos por categoria com paginacao
    const page = 1;
    const limit = 20;
    const skip = (page - 1) * limit;
 
    const results = await products
      .find({ category: "eletronicos", price: { $lte: 5000 } })
      .sort({ price: -1 })
      .skip(skip)
      .limit(limit)
      .toArray();
 
    console.log(`Encontrados ${results.length} produtos`);
 
    // Aggregation: media de preco por categoria
    const avgPrices = await products
      .aggregate([
        { $group: { _id: "$category", avgPrice: { $avg: "$price" }, count: { $sum: 1 } } },
        { $sort: { avgPrice: -1 } },
      ])
      .toArray();
 
    console.log("Media de precos por categoria:", avgPrices);
  } finally {
    await client.close();
  }
}
 
productOperations().catch(console.error);

Serviço de health check e monitoramento com CloudWatch

O segundo exemplo implementa um serviço de health check que publica métricas customizadas no CloudWatch, algo essencial para qualquer setup de produção:

TYPESCRIPT
import { MongoClient, type Db } from "mongodb";
import {
  CloudWatchClient,
  PutMetricDataCommand,
  type MetricDatum,
} from "@aws-sdk/client-cloudwatch";
 
interface HealthStatus {
  isHealthy: boolean;
  latencyMs: number;
  connections: number;
  replicaSetStatus: string;
  documentCount: number;
}
 
class MongoHealthMonitor {
  private mongoClient: MongoClient;
  private cloudWatch: CloudWatchClient;
  private namespace: string;
 
  constructor(mongoClient: MongoClient, region: string) {
    this.mongoClient = mongoClient;
    this.cloudWatch = new CloudWatchClient({ region });
    this.namespace = "Producao/DocumentDB";
  }
 
  async checkHealth(databaseName: string): Promise<HealthStatus> {
    const start = Date.now();
    let health: HealthStatus;
 
    try {
      const db: Db = this.mongoClient.db(databaseName);
 
      // Ping para medir latencia
      const pingResult = await db.command({ ping: 1 });
      const latencyMs = Date.now() - start;
 
      // Status do servidor
      const serverStatus = await db.command({ serverStatus: 1 });
      const connections = serverStatus.connections?.current ?? 0;
 
      // Contagem de documentos na collection principal
      const documentCount = await db
        .collection("products")
        .estimatedDocumentCount();
 
      // Status do replica set
      let replicaSetStatus = "unknown";
      try {
        const replStatus = await db.admin().command({ replSetGetStatus: 1 });
        replicaSetStatus = replStatus.myState === 1 ? "primary" : "secondary";
      } catch {
        replicaSetStatus = "standalone";
      }
 
      health = {
        isHealthy: pingResult.ok === 1,
        latencyMs,
        connections,
        replicaSetStatus,
        documentCount,
      };
 
    } catch (error) {
      const failedHealth: HealthStatus = {
        isHealthy: false,
        latencyMs: Date.now() - start,
        connections: 0,
        replicaSetStatus: "unreachable",
        documentCount: 0,
      };
 
      try {
        await this.publishMetrics(failedHealth);
      } catch (publicationError) {
        throw new AggregateError(
          [error, publicationError],
          "Falharam a coleta de saúde e a publicação das métricas.",
          { cause: error }
        );
      }
      throw error;
    }
 
    await this.publishMetrics(health);
    return health;
  }
 
  private async publishMetrics(health: HealthStatus): Promise<void> {
    const timestamp = new Date();
 
    const metrics: MetricDatum[] = [
      {
        MetricName: "Latency",
        Value: health.latencyMs,
        Unit: "Milliseconds",
        Timestamp: timestamp,
        Dimensions: [{ Name: "Environment", Value: "production" }],
      },
      {
        MetricName: "ActiveConnections",
        Value: health.connections,
        Unit: "Count",
        Timestamp: timestamp,
        Dimensions: [{ Name: "Environment", Value: "production" }],
      },
      {
        MetricName: "DocumentCount",
        Value: health.documentCount,
        Unit: "Count",
        Timestamp: timestamp,
        Dimensions: [{ Name: "Environment", Value: "production" }],
      },
      {
        MetricName: "IsHealthy",
        Value: health.isHealthy ? 1 : 0,
        Unit: "None",
        Timestamp: timestamp,
        Dimensions: [{ Name: "Environment", Value: "production" }],
      },
    ];
 
    for (const metric of metrics) {
      const value = metric.Value;
      if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > 2 ** 360) {
        throw new RangeError(`Valor inválido para a métrica ${metric.MetricName}.`);
      }
    }
 
    await this.cloudWatch.send(
      new PutMetricDataCommand({
        Namespace: this.namespace,
        MetricData: metrics,
      })
    );
  }
}

O monitor envia quatro métricas ao namespace Producao/DocumentDB, com a dimensão Environment=production e valores validados antes da publicação. Falhas de coleta e de envio permanecem distinguíveis: quando ambas ocorrem, AggregateError preserva os dois erros. Instancie a classe com um cliente MongoDB conectado e uma região AWS, trate as rejeições de checkHealth e defina a frequência das verificações na aplicação. Confira os comandos e as permissões suportados pelo banco de destino antes de usar o exemplo em produção.

Guias Relacionados

Explore mais guias técnicos

Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.

Ver todos os guias