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:
# 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 docdbConfigurando o Parameter Group
O parameter group permite ajustar configurações como o profiler e o TTL monitor:
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:
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:
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:
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