MongoDB Indexing Strategies That Actually Save You Money
MongoDBBackendPerformance

MongoDB Indexing Strategies That Actually Save You Money

Real-world MongoDB indexing patterns I've used to drop query times by 100x and Atlas bills by 60%. Compound indexes, ESR rule, partial indexes, TTL — and the indexes you should never create.

HJ
Hassan Javed
February 2026
11 min read

Why this post exists

Three months ago, a client called me about their MongoDB Atlas bill. It had grown from $400/month to $2,800/month over a year. Their data hadn't grown 7x — their query load had. Their index strategy was the problem.

After two days of work, the bill dropped back to $850/month and average query time dropped from 340ms to 12ms. Same data, same queries, dramatically different indexes.

This post is the playbook I used.

Indexing 101 (skip if you know this)

A MongoDB index is a separate B-tree (technically B+ tree) structure that maps field values to document positions. Without an index, a query scans every document in the collection. With the right index, the database walks the tree directly to matching documents.

Indexes cost write performance (every insert/update has to update the indexes too) and disk space. The trade-off is almost always worth it for read-heavy workloads.

The ESR rule (most important thing in this post)

When building a compound index, order the fields by:

Equality → Sort → Range

For a query that filters by status (equality), sorts by totalAmount, and ranges on createdAt:

jscode
db.orders.createIndex({ status: 1, totalAmount: -1, createdAt: 1 })
status first (equality match)
totalAmount next (sort field)
createdAt last (range filter)

If you reverse this — say, put createdAt first — the index can't help with the sort. MongoDB has to load matching docs into memory and sort them. For a 10M-document collection, this is the difference between 5ms and 5 seconds.

I've fixed dozens of slow queries by just reordering fields in the index per ESR.

Compound vs single-field indexes

Rule of thumb: fewer compound indexes that serve multiple queries beat many single-field indexes.

A single compound index { userId: 1, status: 1, createdAt: -1 } serves:

{ userId } queries (uses index prefix)
{ userId, status } queries
{ userId, status, createdAt } queries
Sorts on createdAt within a user's data

The "prefix rule": a compound index can serve any query that uses a left prefix of its fields. Design your compound indexes to maximize prefix matches across your common queries.

Partial indexes — the cost-saver

A partial index only indexes documents matching a filter. This is huge for collections where most documents don't need indexing.

Example: an orders collection with millions of completed orders, but you mostly query active ones. Index only documents with status in ["pending", "processing"]. If 95% of orders are "completed", this index is 5% the size of a full index. Disk savings, RAM savings, faster updates.

Use cases I see often:

Soft-deleted records (deleted: false)
Active subscriptions (status: "active")
Unread notifications (read: false)
Pending tasks (completed: false)

TTL indexes — automatic cleanup

For data with natural expiry (sessions, OTP codes, audit logs, rate-limit records), a TTL (time-to-live) index automatically deletes documents after a duration.

The TTL monitor runs every 60 seconds and removes expired documents. No cron job needed.

I added TTL indexes to one client's audit log collection (which had grown to 200GB) and it shrunk to 12GB over two days. Atlas storage costs dropped immediately.

Wildcard indexes — when you don't know the schema

If you're querying user-defined fields (e.g., a tags map where keys vary), wildcard indexes index every value in a subdocument. Useful for flexible-schema cases. Costs more disk than a fixed-field index, but lets you query unpredictable shapes.

Don't reach for Elasticsearch immediately. MongoDB's built-in text index handles a lot of search needs.

Limitations: no fuzzy matching, no faceting, only one text index per collection. For real search you eventually want Atlas Search (built-in Lucene) or external Elasticsearch. But text indexes get you 80% of the way for free.

Indexes you should never create

Anti-patterns I see:

1.Indexes on every field "just in case" — costs writes, costs disk. Only index fields that are queried.
2.Indexes on low-cardinality fields alone{ isActive: 1 } on a collection where 99% are active. The index is essentially useless; the query planner ignores it.
3.Duplicate indexes{a:1, b:1} and {a:1} together. The second is covered by the first's prefix.
4.Indexes on fields you only update — write cost without read benefit.
5.Indexes you forget to drop — collection migrations leave behind unused indexes. Audit periodically.

How to find your slow queries

Atlas's "Performance Advisor" tab is the fastest answer for managed deployments. It analyzes your slow query log and suggests indexes.

For self-hosted MongoDB: enable profiling at slowms=100, then read the profile collection. You'll see exactly which collection scans are happening.

For a specific query, explain("executionStats") shows whether it used an index and how many docs were scanned. Look for COLLSCAN (bad — full scan) vs IXSCAN (good — index scan).

The one-page playbook

For each slow query:

1.explain("executionStats") — identify what the query planner is doing
2.Apply ESR rule — equality fields first, sort second, range last
3.Consolidate where possible — one compound index serving 3-5 queries beats 5 single-field indexes
4.Use partial indexes when most documents wouldn't be queried
5.Use TTL on time-bound data
6.Drop unused indexes (Atlas Performance Advisor flags these)

Run this loop on every slow query and you'll usually cut latency by 10-100x.

Where I help clients

Most of my MERN engagements include database performance work. If your MongoDB Atlas bill is climbing or queries are getting slow, I do focused 1-2 week index audits — deliverable is a documented index plan, the migration scripts, and before/after benchmarks. Contact me if that's useful.

TL;DR

ESR rule for compound indexes: Equality, Sort, Range
Fewer compound indexes beats many single-field indexes
Partial indexes save costs when most docs aren't queried
TTL indexes auto-clean expired data
Drop unused indexes; they cost writes and disk
Use Atlas Performance Advisor or explain("executionStats")
Related Reads

You might also like