Introduction to Amazon DocumentDB
Amazon DocumentDB (with MongoDB compatibility) is a fully managed, highly durable, and scalable document database service from AWS. It is designed to store, query, and index JSON-like documents at scale, while maintaining compatibility with the MongoDB API. Developers can use existing MongoDB drivers, tools, and application code with minimal changes, but with the added benefits of a managed cloud service — automatic backups, built-in high availability, and seamless scaling.
However, simply provisioning a DocumentDB cluster and connecting your application is not enough. To truly leverage the power of this service, you must understand and apply best practices across three critical dimensions: cost management, security hardening, and performance optimization. This tutorial walks you through each of these pillars with practical, actionable guidance and real code examples you can implement immediately.
Part 1: Cost Optimization Best Practices
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Why Cost Management Matters in DocumentDB
DocumentDB pricing is based on several components: instance hours (the compute capacity of your cluster), storage (the data volume and I/O operations), backups, and data transfer. Without conscious optimization, monthly costs can spiral unexpectedly — especially in development and test environments where idle resources often go unnoticed. Understanding the pricing levers allows you to rightsize your deployment without sacrificing availability or performance.
1. Choose the Right Instance Type and Size
DocumentDB offers two instance families: r (memory optimized) and t (burstable). For production workloads with steady traffic patterns, memory-optimized instances like db.r6g.large or db.r6g.xlarge are recommended because they provide consistent performance and sufficient memory for caching frequently accessed data. For development, staging, or light workloads with intermittent spikes, burstable instances like db.t4g.medium offer significant cost savings while still allowing CPU credits to handle occasional load bursts.
Always start with a smaller instance and monitor CPU, memory, and IOPS via CloudWatch. You can scale up (or change to an r-family instance) when sustained utilization exceeds 60-70%.
# AWS CLI: Modify an existing DocumentDB instance to a larger type
aws docdb modify-db-instance \
--db-instance-identifier your-instance-id \
--db-instance-class db.r6g.xlarge \
--apply-immediately
# If you prefer no downtime, omit --apply-immediately and reboot later
# during a maintenance window
2. Delete or Stop Unused Clusters and Instances
Every running DocumentDB instance accrues hourly charges. For non-production environments (dev, test, QA), consider stopping the cluster when not in use — for example, outside business hours. AWS allows you to stop a DocumentDB cluster for up to 7 days, during which you pay only for storage and backups, not compute.
# Stop a cluster (works for up to 7 days)
aws docdb stop-db-cluster \
--db-cluster-identifier dev-cluster-id
# Start it again when needed
aws docdb start-db-cluster \
--db-cluster-identifier dev-cluster-id
For truly ephemeral environments, automate start/stop schedules using Lambda and CloudWatch Events. Here's a Python Lambda snippet that stops clusters tagged with Environment=Dev at 8 PM every weekday:
import boto3
import os
def lambda_handler(event, context):
client = boto3.client('docdb')
clusters = client.describe_db_clusters()
for cluster in clusters['DBClusters']:
cluster_id = cluster['DBClusterIdentifier']
tags_response = client.list_tags_for_resource(
ResourceName=f'arn:aws:rds:{os.environ["AWS_REGION"]}:'
f'{os.environ["ACCOUNT_ID"]}:cluster:{cluster_id}'
)
tags = {tag['Key']: tag['Value'] for tag in tags_response.get('TagList', [])}
if tags.get('Environment') == 'Dev' and tags.get('AutoStop') == 'true':
client.stop_db_cluster(DBClusterIdentifier=cluster_id)
print(f'Stopped cluster: {cluster_id}')
3. Optimize Storage and IOPS
DocumentDB storage scales automatically in 10 GB increments as your data grows. While this is convenient, it means you pay for every allocated GB. Regularly archive or delete old data that is no longer needed. Use TTL (Time-To-Live) indexes to automatically expire documents after a certain period — this reduces storage footprint and improves query performance over time.
// MongoDB shell: Create a TTL index that auto-deletes documents
// after 90 days based on the "createdAt" field
use myappdb;
db.user_sessions.createIndex(
{ "createdAt": 1 },
{ expireAfterSeconds: 7776000, name: "ttl_90_days" }
);
// Verify the TTL index exists
db.user_sessions.getIndexes();
Also, monitor your backup retention window. DocumentDB retains automated backups for a configurable period (1–35 days). Each day of backup beyond the retention window incurs storage costs. Set the retention period to match your actual RPO (Recovery Point Objective) — for many applications, 7 days is sufficient.
# Set backup retention to 7 days
aws docdb modify-db-cluster \
--db-cluster-identifier production-cluster \
--backup-retention-period 7
4. Use Cluster Snapshot Copies Sparingly
Manual snapshots persist until you delete them, and they are charged at the standard storage rate. Regularly audit and delete obsolete snapshots. If you need long-term retention for compliance, copy snapshots to a cheaper storage tier or export data to S3 in a compressed format.
# List snapshots and delete those older than a specific date
aws docdb describe-db-cluster-snapshots \
--snapshot-type manual \
--query 'DBClusterSnapshots[?SnapshotCreateTime<=`2024-01-01`].DBClusterSnapshotIdentifier' \
--output text | \
xargs -I {} aws docdb delete-db-cluster-snapshot \
--db-cluster-snapshot-identifier {}
5. Monitor and Set Billing Alerts
AWS Budgets allow you to set cost thresholds and receive alerts when spending approaches or exceeds your defined limits. Set monthly budgets for each DocumentDB cluster (or aggregate all DocumentDB usage) and configure SNS notifications.
# Create a monthly AWS Budget for DocumentDB spend using AWS CLI
aws budgets create-budget \
--account-id $(aws sts get-caller-identity --query Account --output text) \
--budget '{
"BudgetName": "DocumentDB-Monthly-Budget",
"BudgetLimit": { "Amount": 500, "UnitCost": "USD" },
"TimeUnit": "MONTHLY",
"BudgetType": "COST",
"CostFilters": {
"Service": ["Amazon DocumentDB"]
}
}' \
--notifications-with-subscribers '[{
"Notification": { "NotificationType": "ACTUAL", "ComparisonOperator": "GREATER_THAN", "Threshold": 80 },
"Subscribers": [{ "SubscriptionType": "EMAIL", "Address": "ops-team@example.com" }]
}]'
Part 2: Security Best Practices
Why Security Configuration is Critical
DocumentDB stores your application's most valuable structured and semi-structured data — user profiles, orders, financial records, session tokens, and more. A security misconfiguration can lead to data breaches, unauthorized access, or accidental data loss. Since DocumentDB is a managed service, AWS handles the underlying infrastructure security (OS patching, physical access), but you are responsible for access control, network security, encryption, and audit logging — this is known as the shared responsibility model.
1. Network Isolation: Place Clusters in a VPC
Never expose a DocumentDB cluster to the public internet. Always deploy clusters within an Amazon VPC, ideally in private subnets (subnets without a route to an Internet Gateway). Access should be granted only to application servers, bastion hosts, or VPN connections within the VPC.
# CloudFormation snippet for a DocumentDB cluster in a private VPC
Resources:
DocDBCluster:
Type: AWS::DocDB::DBCluster
Properties:
DBClusterIdentifier: secure-cluster
MasterUsername: !Ref DocDBUsername
MasterUserPassword: !Ref DocDBPassword
VpcSecurityGroupIds:
- !Ref DocDBSecurityGroup
DBSubnetGroupName: !Ref DocDBSubnetGroup
DocDBSubnetGroup:
Type: AWS::DocDB::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Private subnets for DocumentDB
SubnetIds:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetB
2. Security Groups: Least Privilege Access
Configure security groups to allow inbound traffic only on port 27017 (the DocumentDB port) from the specific CIDR blocks or security group IDs of your application servers. Never use 0.0.0.0/0 as a source.
# AWS CLI: Create a security group and add a restricted rule
aws ec2 create-security-group \
--group-name docdb-app-access \
--description "Allow app servers to reach DocumentDB" \
--vpc-id vpc-0abc123def456
aws ec2 authorize-security-group-ingress \
--group-id sg-0xyz789 \
--protocol tcp \
--port 27017 \
--source-group sg-appserver-sg-id
3. Encryption: Enable at Rest and in Transit
DocumentDB supports encryption at rest using AWS KMS keys. Enable this at cluster creation time — it cannot be added after the fact without restoring from a snapshot. For encryption in transit, use TLS/SSL connections from your application. DocumentDB presents a self-signed certificate by default; you should verify the certificate chain in your client configuration.
# Create a cluster with encryption at rest enabled
aws docdb create-db-cluster \
--db-cluster-identifier encrypted-cluster \
--engine docdb \
--master-username admin \
--master-user-password 'SecureP@ssw0rd!' \
--storage-encrypted \
--kms-key-id arn:aws:kms:us-east-1:123456789:key/abc-def-1234
On the application side, always connect with TLS enabled. Here's how to configure a Node.js MongoDB client to require TLS and verify the server certificate:
// Node.js MongoDB driver with TLS encryption in transit
const { MongoClient } = require('mongodb');
const fs = require('fs');
// Download the Amazon DocumentDB CA certificate from:
// https://docs.aws.amazon.com/documentdb/latest/developerguide/connect-with-tls.html
const ca = fs.readFileSync('./global-bundle.pem');
const client = new MongoClient(
'mongodb://admin:password@docdb-cluster-endpoint:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false',
{
tls: true,
tlsCAFile: './global-bundle.pem',
// For production, also verify hostname
checkServerIdentity: (host, cert) => {
// Implement hostname validation logic here
return undefined; // undefined means valid
}
}
);
async function connect() {
await client.connect();
console.log('Connected securely to DocumentDB');
}
connect();
4. Authentication and Authorization: IAM Database Users
Instead of hard-coding long-lived username/password credentials in your application, use IAM-based authentication. DocumentDB allows you to create database users that authenticate using AWS IAM roles or users, leveraging temporary credentials. This reduces the risk of credential leaks and simplifies credential rotation.
First, enable IAM authentication on the cluster, then create a database user mapped to an IAM role:
# Enable IAM DB authentication on the cluster
aws docdb modify-db-cluster \
--db-cluster-identifier secure-cluster \
--enable-iam-database-authentication true \
--apply-immediately
# Connect to DocumentDB as the master user and create an IAM user
# Inside the mongo shell:
use admin;
db.createUser({
user: "app-service-role",
mechanisms: ["aws"],
roles: [{ role: "readWrite", db: "mydb" }]
});
Your application then uses the AWS SDK to generate an authentication token before connecting:
# Python example: Generate an IAM auth token and connect
import boto3
import pymongo
from urllib.parse import quote_plus
def get_iam_token(cluster_endpoint, region):
client = boto3.client('docdb', region_name=region)
token = client.generate_db_auth_token(
DBClusterIdentifier='secure-cluster',
Region=region,
Username='app-service-role'
)
return token
token = get_iam_token('your-cluster.docdb.amazonaws.com', 'us-east-1')
encoded_token = quote_plus(token)
uri = f'mongodb://app-service-role:{encoded_token}@your-cluster.docdb.amazonaws.com:27017/?tls=true&tlsCAFile=global-bundle.pem&authSource=$external&authMechanism=PLAIN'
client = pymongo.MongoClient(uri)
db = client.mydb
print(db.list_collection_names())
5. Audit Logging and Monitoring
Enable DocumentDB audit logs to track database activity — who is accessing what data, from where, and when. These logs are published to Amazon CloudWatch Logs and can be queried, alerted on, or exported to SIEM tools.
# Enable audit logs and publish to CloudWatch
aws docdb modify-db-cluster \
--db-cluster-identifier secure-cluster \
--cloudwatch-logs-export-configuration '{
"EnableLogTypes": ["audit"]
}' \
--apply-immediately
Combine this with CloudWatch Alarms for suspicious patterns — for example, a sudden spike in failed authentication attempts:
# CloudWatch alarm for excessive failed auth attempts
aws cloudwatch put-metric-alarm \
--alarm-name DocDB-FailedAuth-Spike \
--alarm-description "Alert on >50 failed auth attempts in 5 minutes" \
--metric-name DatabaseAuthenticationFailures \
--namespace AWS/DocDB \
--statistic Sum \
--period 300 \
--evaluation-periods 1 \
--threshold 50 \
--comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:us-east-1:123456789:ops-alert-topic
6. Principle of Least Privilege for Database Users
Within the database itself, avoid granting overly broad roles. Create specific roles with scoped permissions. For example, a reporting service should have read-only access to specific collections, not readWrite on the entire database.
// MongoDB shell: Create a read-only user for reporting
use myappdb;
db.createUser({
user: "reporting-service",
pwd: passwordPrompt(), // interactive password prompt
roles: [
{ role: "read", db: "myappdb" }
]
});
// Even more granular: restrict to specific collections
db.createRole({
role: "ordersReader",
privileges: [
{ resource: { db: "myappdb", collection: "orders" }, actions: ["find"] },
{ resource: { db: "myappdb", collection: "customers" }, actions: ["find"] }
],
roles: []
});
db.grantRolesToUser("reporting-service", ["ordersReader"]);
Part 3: Performance Optimization Best Practices
Why Performance Tuning is Essential
DocumentDB performance directly impacts application latency, user experience, and infrastructure costs. A poorly optimized cluster may require larger, more expensive instances to achieve acceptable response times. Conversely, a well-tuned cluster can handle higher throughput on smaller instances, saving money while delighting users. Performance optimization spans schema design, query patterns, indexing strategy, connection management, and monitoring.
1. Schema Design: Embed or Reference Thoughtfully
Document databases give you flexibility in data modeling. The key decision is whether to embed related data within a single document or reference it across collections. Embedding reduces the need for joins (which DocumentDB does not natively support) and improves read performance by fetching everything in one operation. However, deeply nested documents can grow large, increasing I/O and making updates complex.
As a rule of thumb, embed data that is frequently accessed together and has a bounded size. Reference data that changes independently or would cause unbounded document growth.
// Good: Embed bounded, co-accessed data — order line items
db.orders.insertOne({
_id: ObjectId("..."),
customerId: "cust_123",
orderDate: ISODate("2025-03-15T10:00:00Z"),
items: [
{ sku: "P100", qty: 2, price: 29.99 },
{ sku: "P200", qty: 1, price: 49.99 }
],
total: 109.97
});
// Avoid: Embedding unbounded data — a user's entire order history
// Instead, reference orders by userId and query separately
db.users.insertOne({
_id: "user_456",
name: "Jane Doe",
email: "jane@example.com"
// Do NOT embed hundreds of orders here
});
2. Indexing Strategy: Build Indexes That Match Your Queries
Indexes are the single most impactful performance lever in DocumentDB. Without proper indexes, queries perform collection scans — reading every document — which is slow and consumes CPU and IOPS. Always analyze your application's query patterns and create indexes that support them.
Use the explain() method to verify that queries use indexes:
// MongoDB shell: Check query execution plan
db.orders.find({ customerId: "cust_123" }).explain("executionStats");
// Look for "winningPlan.stage": "IXSCAN" (index scan) vs "COLLSCAN" (bad)
// If you see COLLSCAN, create an index:
db.orders.createIndex({ customerId: 1 });
// Compound indexes support queries with multiple filters and sorts
db.orders.createIndex({ customerId: 1, orderDate: -1 });
// Covering indexes include all projected fields to avoid fetching documents
db.orders.createIndex(
{ customerId: 1, orderDate: -1, total: 1 },
{ name: "customer_order_summary_idx" }
);
3. Avoid Common Query Anti-Patterns
Certain query operations are inherently expensive on DocumentDB. Be especially cautious with:
- Leading wildcards in regex: Queries like
{ name: /.*smith/ }cannot use indexes effectively. - Unindexed $lookup operations: Aggregation pipelines with
$lookupon unindexed foreign fields cause full collection scans on the joined collection. - Sorting without an index: Large result sets with
sort()and no matching index cause in-memory sorting that can exhaust resources. - Using $ne, $nin, or $nor as the primary filter: These operators often preclude index usage.
// Bad: Leading wildcard regex — forces index scan then filter
db.customers.find({ lastName: /.*son/ });
// Better: Use a full-text search service or exact prefix match
db.customers.find({ lastName: /^Johnson/ }); // Prefix match can use index
// Bad: Unindexed sort on large collection
db.orders.find({ status: "pending" }).sort({ createdAt: -1 });
// Better: Create compound index that covers both filter and sort
db.orders.createIndex({ status: 1, createdAt: -1 });
4. Connection Management and Pooling
DocumentDB clusters have finite connection limits based on instance size. Exceeding these limits causes connection drops and degraded performance. Use connection pooling in your application to reuse connections efficiently. Configure pool size, timeouts, and retry logic appropriately.
// Node.js: Configure connection pool with sensible defaults
const client = new MongoClient(uri, {
maxPoolSize: 50, // Limit concurrent connections
minPoolSize: 5, // Maintain a minimum pool for responsiveness
maxIdleTimeMS: 30000, // Close idle connections after 30s
connectTimeoutMS: 5000, // Fail fast if initial connection takes >5s
serverSelectionTimeoutMS: 5000,
retryWrites: false, // DocumentDB doesn't support retryable writes
retryReads: true // Retry reads on transient failures
});
For Python applications using PyMongo, similar principles apply:
# Python: Configure PyMongo with connection pooling
from pymongo import MongoClient
client = MongoClient(
uri,
maxPoolSize=50,
minPoolSize=5,
maxIdleTimeMS=30000,
connectTimeoutMS=5000,
serverSelectionTimeoutMS=5000,
retryWrites=False,
retryReads=True
);
5. Monitor Key Performance Metrics
Use Amazon CloudWatch to track critical DocumentDB metrics and identify bottlenecks before they impact users. The most important metrics include:
- CPUUtilization: Sustained high CPU (>70%) indicates the need for a larger instance or query optimization.
- FreeableMemory: Low memory can cause swapping and slow queries. Memory-optimized instances help here.
- DatabaseConnections: Track current connections against your instance's maximum to avoid exhaustion.
- ReadIOPS and WriteIOPS: High IOPS with low throughput suggests inefficient queries or missing indexes.
- VolumeBytesUsed: Monitor storage growth trends to forecast costs.
# AWS CLI: Fetch CPU utilization for the last hour
aws cloudwatch get-metric-statistics \
--namespace AWS/DocDB \
--metric-name CPUUtilization \
--dimensions Name=DBInstanceIdentifier,Value=your-instance-id \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 300 \
--statistics Average \
--query 'sort_by(Datapoints, &Timestamp)[-10:]' \
--output table
6. Use Read Replicas for Read-Heavy Workloads
DocumentDB clusters support up to 15 read replicas that share the same storage volume. For read-heavy applications (analytics dashboards, reporting services, search functionality), distribute read traffic across replicas to reduce load on the primary instance. Use the secondaryPreferred read preference in your connection string to automatically route reads to replicas.
// Connection string with read preference for replica distribution
const uri = 'mongodb://admin:password@cluster-endpoint:27017/' +
'?tls=true&tlsCAFile=global-bundle.pem' +
'&replicaSet=rs0' +
'&readPreference=secondaryPreferred' +
'&retryWrites=false';
For applications with distinct read and write patterns, maintain two separate connection pools — one pointing to the cluster endpoint (which routes to the primary for writes) and another pointing to the read-only replica endpoint for heavy read operations.
# Create a separate reader endpoint for read-only traffic
# The reader endpoint automatically load-balances across replicas
aws docdb describe-db-clusters \
--db-cluster-identifier your-cluster \
--query 'DBClusters[0].ReaderEndpoint' \
--output text
# Example output: your-cluster-ro.cluster-abc123.us-east-1.docdb.amazonaws.com
7. Batch Operations and Bulk Writes
When inserting or updating many documents, use bulk operations instead of individual write commands. This reduces network round trips and improves throughput dramatically.
// MongoDB shell: Bulk insert 1000 documents efficiently
const bulk = db.sensor_readings.initializeUnorderedBulkOp();
for (let i = 0; i < 1000; i++) {
bulk.insert({
sensorId: `sensor_${i % 50}`,
timestamp: new Date(),
value: Math.random() * 100,
unit: "celsius"
});
}
const result = bulk.execute();
print(`Inserted ${result.nInserted} documents`);
For ordered operations where you need to stop on the first error, use initializeOrderedBulkOp() instead. In Node.js, the equivalent pattern looks like this:
// Node.js: Bulk write with the MongoDB driver
const collection = client.db('iot').collection('sensor_readings');
const docs = Array.from({ length: 1000 }, (_, i) => ({
sensorId: `sensor_${i % 50}`,
timestamp: new Date(),
value: Math.random() * 100,
unit: "celsius"
}));
const result = await collection.insertMany(docs, { ordered: false });
console.log(`Inserted ${result.insertedCount} documents`);
8. Optimize Aggregation Pipelines
Aggregation pipelines are powerful but can be resource-intensive. Always place $match stages as early as possible in the pipeline to reduce the number of documents processed in subsequent stages. Use indexes to support the fields in your $match stage, and consider using $project to trim unnecessary fields early.
// Inefficient: $match comes after $group — processes all documents
db.orders.aggregate([
{ $group: { _id: "$customerId", totalSpent: { $sum: "$total" } } },
{ $match: { totalSpent: { $gt: 1000 } } } // Too late!
]);
// Efficient: $match first, uses index, reduces pipeline input
db.orders.aggregate([
{ $match: { orderDate: { $gte: ISODate("2025-01-01") } } },
{ $project: { customerId: 1, total: 1 } },
{ $group: { _id: "$customerId", totalSpent: { $sum: "$total" } } },
{ $match: { totalSpent: { $gt: 1000 } } }
]);
Bringing It All Together: A Practical Checklist
To summarize the best practices across all three dimensions, here's a consolidated checklist you can use when deploying or auditing a DocumentDB cluster:
- Cost: Use burstable instances for dev/test, stop clusters when idle, set TTL indexes to auto-expire old data, configure backup retention to match RPO, and create AWS Budget alerts.
- Security: Deploy in private VPC subnets, restrict security group ingress to app servers only, enable encryption at rest and in transit, use IAM database authentication, enable audit logging, and apply least-privilege database roles.
- Performance: Design schemas with appropriate embedding vs. referencing, create indexes that match actual query patterns (verify with
explain), avoid regex leading wildcards and unindexed sorts, use connection pooling, monitor key CloudWatch metrics, distribute reads across replicas, and use bulk write operations.
Conclusion
Amazon DocumentDB offers a powerful, fully managed document database experience compatible with the MongoDB ecosystem. However, realizing its full potential requires deliberate attention to cost management, security hardening, and performance optimization. By rightsizing instances, automating resource scheduling, encrypting data at rest and in transit, locking down network access, designing efficient schemas and indexes, and continuously monitoring metrics, you can build applications that are not only performant and secure but also cost-effective at scale. Apply the code examples and patterns from this tutorial incrementally — start with one area, measure the impact, and iterate. The result will be a robust, production-grade DocumentDB deployment that serves your users reliably while keeping your AWS bill predictable.