ブログCloud & Infrastructure
Cloud & Infrastructure

MongoDB Atlas vs Self-Hosted MongoDB: Total Cost, Performance, and Migration Guide

Should you pay MongoDB Atlas prices or run your own cluster? This guide breaks down the real total cost of ownership, performance differences at scale, when self-hosting makes financial sense, and how to migrate between the two without downtime.

A

Alex Thompson

CEO & Cloud Architecture Expert at ZeonEdge with 15+ years building enterprise infrastructure.

March 26, 2026
22 分で読めます

The MongoDB Deployment Decision

MongoDB Atlas is convenient, polished, and priced accordingly. At startup scale (under $500/month), Atlas is almost always the right choice — you get automated backups, multi-region replication, monitoring, and zero operational overhead. But as your data grows, Atlas costs scale aggressively. A 3-node M40 Atlas cluster in us-east-1 costs around $2,200/month. The equivalent EC2 instances cost $600/month.

That 3.6x price difference is the Atlas premium. You're paying for: managed operations, automatic failover, global cluster routing, Atlas Search, Vector Search, Data Federation, and not having to hire a DBA. Whether that premium is worth it depends entirely on your engineering team's capacity and the business cost of operational incidents.

Atlas Tier Breakdown

MongoDB Atlas Pricing (2026, AWS us-east-1)

Shared Clusters (M0-M5):
  M0 (Free) — 512MB storage, shared RAM, limited to 500 connections
  M2 — $9/month, 2GB storage
  M5 — $25/month, 5GB storage
  ⚠ Shared clusters have NO SLA, throttled IOPS, paused after 60 days idle

Dedicated Clusters:
  M10 — $0.08/hr = ~$58/month (2GB RAM, 10GB NVMe)
  M20 — $0.20/hr = ~$144/month (4GB RAM, 20GB NVMe)
  M30 — $0.54/hr = ~$389/month (8GB RAM, 40GB NVMe)
  M40 — $1.04/hr = ~$749/month per node × 3 nodes = $2,247/month
  M50 — $2.00/hr = ~$1,440/month per node × 3 nodes = $4,320/month
  M60 — $3.95/hr = ~$2,844/month per node × 3 nodes = $8,532/month

Additional costs:
  Backup: ~20% of cluster cost
  Data transfer out: $0.09/GB (AWS egress)
  Atlas Search: included, but runs on cluster resources
  
Example: M40 cluster + 2TB storage + backups + egress = ~$3,200/month

Self-Hosted Cost Analysis

Self-Hosted on AWS (3-node replica set, equivalent to M40)

EC2 Instances:
  r6i.2xlarge (8 vCPU, 64GB RAM) × 3 nodes = $0.504/hr × 3 = $1.08/hr
  Reserved (1-year): saves 40% → $0.65/hr × 3 = ~$1,404/month

EBS Storage:
  gp3: 500GB × 3 nodes at $0.08/GB-month = $120/month
  Provisioned IOPS (io1): 3000 IOPS × 3 = +$195/month

Backup (AWS Backup or mongodump to S3):
  S3: 500GB snapshots × 30 days lifecycle = ~$12/month
  EBS snapshots: $25/month

Monitoring (DataDog/New Relic or self-hosted Prometheus/Grafana):
  Prometheus + Grafana (self-hosted): $0 (just time)
  DataDog 3 hosts: $57/month

Operational cost (engineer time):
  Initial setup: 40 hours × $100/hr = $4,000 one-time
  Ongoing: 4 hours/month × $100/hr = $400/month

Total: ~$2,156/month (vs Atlas M40 at $3,200/month)
Annual savings: $12,528 — minus ~$5,000 initial setup = $7,528 first year

At M50+ the savings compound significantly:
  Atlas M50 × 3 = $4,320/month
  EC2 r6i.4xlarge × 3 reserved = $2,100/month
  Annual savings: $26,640

When Atlas Wins

Choose Atlas when:

1. Team size < 5 engineers
   You cannot afford dedicated database expertise
   Every hour spent on MongoDB ops is an hour not building product

2. Compliance requirements (HIPAA, SOC2, PCI-DSS)
   Atlas is certified — audit reports are provided
   Self-hosted certification is expensive ($50k+ in consulting)

3. Multi-region requirements
   Atlas Global Clusters are genuinely hard to replicate
   Cross-region reads, regional write leaders, auto-routing
   
4. Atlas-specific features you depend on:
   Atlas Vector Search (integrated ML vector store)
   Atlas Search (Lucene-based full-text, no separate Elasticsearch)
   Atlas Data Lake (federated queries across S3 + MongoDB)
   Charts (embedded BI without Tableau)
   
5. Your database is small (< 100GB)
   Savings don't justify complexity until significant scale

6. Startups: speed > cost
   Atlas free tier → M10 → M20 path is a fast upgrade path
   No migration pain between tiers

When Self-Hosted Wins

Choose self-hosted when:

1. Monthly Atlas bill exceeds $5,000
   Engineering time cost is fully justified

2. You have database engineering expertise
   Or can hire it — a DBA at $120k/year pays for itself above $10k/month Atlas

3. Data sovereignty requirements
   Some industries/countries require specific data residency
   Self-hosted gives you full control over where data lives

4. Custom storage engines or WiredTiger tuning
   Atlas restricts many mongod configuration options
   Self-hosted: full mongod.conf access

5. Existing infrastructure team capacity
   If ops team already manages Kubernetes, self-hosted adds minimal burden

6. Very high write throughput
   Atlas throttles IOPS per tier
   Self-hosted can provision NVMe locally attached storage (10x cheaper IOPS)

Self-Hosted Architecture: Production-Grade Setup

# docker-compose.yml for local dev (not production)
version: '3.8'
services:
  mongo1:
    image: mongo:7.0
    command: mongod --replSet rs0 --keyFile /etc/mongo/keyfile --bind_ip_all
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: {{ secrets.mongo_root_password }}
    volumes:
      - mongo1_data:/data/db
      - ./mongo-keyfile:/etc/mongo/keyfile:ro
    ports:
      - "27017:27017"
      
  mongo2:
    image: mongo:7.0
    command: mongod --replSet rs0 --keyFile /etc/mongo/keyfile --bind_ip_all
    volumes:
      - mongo2_data:/data/db
      - ./mongo-keyfile:/etc/mongo/keyfile:ro
  
  mongo3:
    image: mongo:7.0
    command: mongod --replSet rs0 --keyFile /etc/mongo/keyfile --bind_ip_all
    volumes:
      - mongo3_data:/data/db
      - ./mongo-keyfile:/etc/mongo/keyfile:ro
# Production deployment on EC2 — mongod.conf tuning
cat > /etc/mongod.conf << 'EOF'
systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logRotate: reopen

storage:
  dbPath: /data/db
  engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 48  # ~70% of RAM on 64GB instance
      journalCompressor: zstd
    collectionConfig:
      blockCompressor: zstd
    indexConfig:
      prefixCompression: true

net:
  port: 27017
  bindIp: 0.0.0.0
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongo/mongo.pem
    CAFile: /etc/ssl/mongo/ca.pem

security:
  authorization: enabled
  keyFile: /etc/mongo/keyfile

replication:
  replSetName: "rs0"
  oplogSizeMB: 10240  # 10GB oplog for large write workloads

operationProfiling:
  slowOpThresholdMs: 100  # Log queries > 100ms
  mode: slowOp

setParameter:
  enableLocalhostAuthBypass: 0
EOF

Replica Set Initialization and Monitoring

// Initialize replica set (run once on primary)
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1.internal:27017", priority: 2 },
    { _id: 1, host: "mongo2.internal:27017", priority: 1 },
    { _id: 2, host: "mongo3.internal:27017", priority: 1, 
      hidden: false, slaveDelay: 0 }
  ]
})

// Check replica set status
rs.status()

// Add hidden delayed secondary for point-in-time recovery
rs.add({
  host: "mongo4.internal:27017",
  hidden: true,
  slaveDelay: 3600,  // 1-hour delay — protection against accidental deletes
  priority: 0,
  votes: 0
})
# Prometheus MongoDB exporter
version: '3.8'
services:
  mongodb-exporter:
    image: percona/mongodb_exporter:0.40
    command:
      - '--mongodb.uri=mongodb://monitor:password@mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0'
      - '--collect-all'
    ports:
      - "9216:9216"

# Key Prometheus alerts to configure:
# - mongodb_up == 0 (node down)
# - mongodb_replset_member_health{state!="PRIMARY"} == 0 (secondary down)
# - mongodb_ss_opcounters{type="query"} rate spike (query storm)
# - mongodb_wiredtiger_cache_bytes_currently_in_cache / cacheSizeGB > 0.95 (cache pressure)
# - Replication lag: rs.status().members[N].optimeDate delta > 10s

Migrating Atlas → Self-Hosted (Zero Downtime)

# Method 1: mongodump/mongorestore (small databases < 100GB)

# 1. Create consistent snapshot from Atlas secondary
mongodump   --uri="mongodb+srv://user:pass@cluster0.abc123.mongodb.net/mydb"   --readPreference=secondary   --oplog   --gzip   --out=/backup/atlas-dump

# 2. Restore to self-hosted
mongorestore   --uri="mongodb://admin:pass@mongo1.internal:27017/mydb?replicaSet=rs0"   --oplogReplay   --gzip   --drop   /backup/atlas-dump

# 3. Tail Atlas oplog to apply changes during migration window
# (mongodump --oplog captures a consistent point-in-time)
# Method 2: mongosync (online sync, recommended for large databases)
# mongosync = MongoDB's official live sync tool

# Install mongosync
curl -LO https://downloads.mongodb.com/mongosync/mongosync-linux-x86_64-1.9.0.tgz
tar xf mongosync-linux-x86_64-1.9.0.tgz

# Start sync (runs continuously until you cut over)
./mongosync   --cluster0 "mongodb+srv://user:pass@cluster0.abc123.mongodb.net"   --cluster1 "mongodb://admin:pass@mongo1.internal:27017/?replicaSet=rs0"   --config mongosync.json

# mongosync.json
cat > mongosync.json << 'EOF'
{
  "id": "migration-001",
  "logPath": "/var/log/mongosync.log",
  "verbosity": "INFO"
}
EOF

# Monitor sync progress via REST API
curl localhost:27182/api/v1/progress
# {"progress": {"state": "RUNNING", "lagTimeSeconds": 2, ...}}

# When lag is < 5 seconds, you're ready to cut over:
# 1. Stop writes to Atlas (maintenance window or feature flag)
# 2. Wait for lagTimeSeconds: 0
# 3. Run: curl -X POST localhost:27182/api/v1/commit
# 4. Update application connection strings
# 5. Verify writes on self-hosted
# 6. Done!

Migrating Self-Hosted → Atlas

# Use Atlas Live Migration (free, from Atlas UI)
# Or use mongodump/restore for simpler databases

# Atlas UI: Database → Migrate Data to Atlas
# Provide your self-hosted connection string
# Atlas pulls the data — your cluster is the source
# Atlas handles: initial sync + change stream tail + cutover coordination

# For scripted migration:
mongodump   --uri="mongodb://admin:pass@mongo1.internal:27017/?replicaSet=rs0"   --readPreference=secondary   --oplog   --gzip   --out=/backup/self-hosted-dump

mongorestore   --uri="mongodb+srv://user:pass@cluster0.abc123.mongodb.net"   --oplogReplay   --gzip   --drop   /backup/self-hosted-dump

Atlas Features You'll Lose When Self-Hosting

Atlas-exclusive features:
  ✗ Atlas Vector Search (replace with pgvector/Qdrant/Weaviate)
  ✗ Atlas Search (replace with Elasticsearch/OpenSearch/Typesense)
  ✗ Atlas Data Federation (replace with Trino/Presto or dbt)
  ✗ Atlas Charts (replace with Grafana + MongoDB datasource plugin)
  ✗ Atlas Triggers (replace with Change Streams + your own worker)
  ✗ Online Archive (replace with scheduled mongodump to S3 + Athena)
  ✗ Global Clusters (replace with manual cross-region replica sets)
  ✗ Automated cluster sizing recommendations
  ✗ One-click index performance advisor
  ✗ Built-in Performance Advisor (replace with explain() + custom Grafana)
  ✗ Atlas App Services (Realm SDK) — replace with self-hosted backend

What you keep:
  ✓ All standard MongoDB features
  ✓ Change Streams
  ✓ Aggregation Pipeline
  ✓ Transactions (multi-document)
  ✓ Time Series Collections
  ✓ Full BSON type support

Hybrid Approach: Best of Both

Pattern: Atlas for production, self-hosted for analytics/dev

Production: Atlas M40 (high availability, managed backups)
  - All customer-facing reads/writes
  - Rely on Atlas SLA for uptime guarantee

Development/Staging: Self-hosted (cheap EC2 or local)
  - Developers run local MongoDB via Docker
  - Staging uses a small EC2 t3.large (~$60/month)
  - Saves $300-500/month vs Atlas M20/M30 for non-prod

Analytics: Self-hosted on Kubernetes (optional)
  - Long-running aggregation queries
  - Don't compete with production IOPS
  - Load nightly backups from Atlas backup chain

This hybrid saves 30-40% vs full Atlas while keeping
production on the managed service you can rely on.

Conclusion

Atlas is not overpriced — it's appropriately priced for what it delivers. Managed failover, global distribution, Atlas Search, Vector Search, and zero DBA overhead are genuinely valuable features. The question is whether your team can capture equivalent value from the difference in cost.

For teams under 10 engineers or monthly costs under $3,000, Atlas almost always wins on total cost of ownership once you account for engineering time. Above $5,000/month with a capable infrastructure team, self-hosting on Kubernetes with Percona Operator, Prometheus monitoring, and scheduled backups to S3 usually delivers meaningful savings with acceptable operational overhead.

A

Alex Thompson

CEO & Cloud Architecture Expert at ZeonEdge with 15+ years building enterprise infrastructure.

インフラストラクチャの変革をご検討中ですか?

同様の結果を達成するためにお手伝いできることについて話し合いましょう。