Unit 4: Indexing and Aggregation Framework
I. Orientation
MongoDB indexing and aggregation provide complementary mechanisms for intelligent data access: indexes reduce the work required to locate documents, while the aggregation framework transforms documents into analytical results through ordered processing stages.
- Document model: MongoDB stores BSON documents in collections; fields may contain scalar values, arrays, embedded documents, or references.
- Index principle: An index maintains selected field values in an ordered structure, commonly a B-tree, together with references to their documents.
- Query optimization: The query planner compares candidate execution plans and selects an efficient strategy using available indexes and query constraints.
- Aggregation principle: A pipeline passes documents through stages such as
$match,$group,$project, and$sort, with each stage producing input for the next. - Index trade-off: Indexes accelerate reads but consume storage and add work to inserts, updates, and deletions.
- Pipeline convention: Aggregation stages are written as an ordered array; changing stage order can change both results and performance.
- Execution goal: Efficient MongoDB workloads minimize documents examined, avoid unnecessary memory use, and return only required fields.
II. Index Structures — Organizing Efficient Access Paths
A. Single Indexes
A single-field index stores values from one document field and is most effective when queries repeatedly filter or sort by that field.
- Creation syntax:
1requests ascending order and-1requests descending order.
db.students.createIndex({ studentId: 1 })- Equality lookup: With the index above, MongoDB can locate
{ studentId: 1042 }without scanning every document instudents. - Sorting support: An index
{ age: 1 }supports both ascending and reverse descending traversal for a query such as:
db.students.find({}).sort({ age: -1 })- Unique constraint: A unique index rejects duplicate indexed values.
db.users.createIndex({ email: 1 }, { unique: true })- Sparse behavior:
{ sparse: true }indexes only documents containing the field, whereas a normal single-field index also represents missing ornullvalues according to MongoDB indexing rules. - Partial index:
partialFilterExpressionindexes only documents satisfying a condition.
db.orders.createIndex(
{ customerId: 1 },
{ partialFilterExpression: { status: "OPEN" } }
)- Array field: Indexing an array creates a multikey index with entries for array elements; MongoDB manages this automatically.
- Limitation: A field useful for one predicate may not help another. An index on
departmentdoes not directly optimize a filter containing onlysalary.
B. Compound Indexes
A compound index stores two or more fields in a defined order, enabling efficient multi-condition filters and compatible sorts.
- Field order: The index
{ department: 1, salary: -1 }is first ordered bydepartment, then by descendingsalarywithin each department. - Prefix rule: The index supports queries using its leading prefixes:
{ department: "Sales" }{ department: "Sales", salary: { $gt: 60000 } }- It is generally less useful for a query on
salaryalone becausedepartmentis the leading field.
- ESR guideline: Compound indexes are commonly designed in Equality, Sort, Range order. For an equality filter on
status, a sort oncreatedAt, and a range ontotal, a candidate is:
db.orders.createIndex({ status: 1, createdAt: -1, total: 1 })- Sort compatibility:
{ category: 1, price: -1 }supports sorting by the same pattern or its complete reverse, subject to constraints on preceding fields. - Selectivity: A field that eliminates many documents is selective, but selectivity must be balanced against prefix use and ESR ordering.
- Worked example: For:
db.products.find({
category: "Laptop",
price: { $lte: 1000 }
}).sort({ rating: -1 })a candidate index is { category: 1, rating: -1, price: 1 }: category is equality, rating supplies sort order, and price is a range.
- Limitation: Redundant compound indexes increase storage and write cost; existing prefixes should be checked before creating another index.
C. Text Indexes
A text index supports language-aware search over string content by indexing terms rather than requiring exact whole-field equality.
- Creation syntax: Multiple fields can participate in one text index.
db.articles.createIndex({
title: "text",
body: "text"
})- Search operator:
$textsearches fields included in the collection’s text index.
db.articles.find({
$text: { $search: "database indexing" }
})- Term behavior: An unquoted search commonly matches documents containing search terms, while an escaped quoted phrase requests phrase matching.
db.articles.find({
$text: { $search: "\"aggregation pipeline\"" }
})- Relevance score: The computed
textScorecan be projected and sorted.
db.articles.find(
{ $text: { $search: "NoSQL analytics" } },
{ score: { $meta: "textScore" }, title: 1 }
).sort({ score: { $meta: "textScore" } })- Weights: Important fields can influence ranking more strongly.
db.articles.createIndex(
{ title: "text", body: "text" },
{ weights: { title: 5, body: 1 } }
)- Language processing: Text indexes can apply stemming and stop-word rules based on the configured language.
- Limitation: A collection can have only one text index, although that index may cover several fields. Text search is less capable than specialized search systems for fuzzy matching, advanced ranking, and autocomplete.
III. Query Performance — Measuring Index Effectiveness
A. Performance with Indexing
Index performance depends on workload-specific measurements rather than the mere presence of an index.
- Collection scan:
COLLSCANexamines collection documents directly; its cost tends to grow with collection size. - Index scan:
IXSCANtraverses index keys and then may fetch matching documents. A selective index can reduce examined documents from millions to a small result set. - Selectivity ratio: A practical indicator is:
Selectivity = matching documents / total documentsHere, a smaller ratio usually indicates a more selective predicate. A query matching 100 of 1,000,000 documents has selectivity 0.0001.
- Write overhead: Every inserted or changed indexed value may require index maintenance. Ten indexes can make a write substantially more expensive than one index.
- Memory behavior: Frequently accessed index pages perform best when the working set fits available RAM; disk reads increase latency.
- Sort optimization: An index-compatible sort avoids an in-memory blocking sort and can reduce memory consumption.
- Low-cardinality fields: An index on a Boolean field such as
activemay be ineffective when almost every document hasactive: true. - Operational discipline: Use
getIndexes()to inspect definitions and$indexStatsto observe index-access counts before removing apparently unused indexes.
B. Covered Queries and explain()
A covered query is answered entirely from index entries, while explain() reveals whether MongoDB actually used that access path efficiently.
- Coverage conditions: All filter and returned fields must be present in the same index, and the query must avoid conditions that require document inspection.
- Identifier exclusion: Because
_idis returned by default, it must be excluded when_idis absent from the index.
db.users.createIndex({ email: 1, status: 1 })
db.users.find(
{ email: "a@example.com" },
{ _id: 0, email: 1, status: 1 }
)- Coverage evidence: An execution plan containing
IXSCANwithout a documentFETCH, together withtotalDocsExamined: 0, indicates index-only retrieval. - Execution inspection:
db.users.find({ status: "ACTIVE" })
.explain("executionStats")- Key metrics:
nReturned: Number of documents returned.totalKeysExamined: Number of index entries inspected.totalDocsExamined: Number of collection documents inspected.executionTimeMillis: Observed execution time for that run.
- Plan interpretation: A strong selective plan often has examined counts close to
nReturned; 50 returned documents with 500,000 documents examined signals poor filtering efficiency. - Caution: Execution time varies with caching and system load, so stage structure and examination counts are often more reliable for diagnosis.
IV. Aggregation Framework — Transforming Documents into Results
A. Aggregation Pipeline Concepts
An aggregation pipeline is an ordered sequence of stages that filters, reshapes, combines, and summarizes documents.
- Stage flow: Each stage receives documents from the preceding stage and emits transformed documents.
db.orders.aggregate([
{ $match: { status: "PAID" } },
{ $group: {
_id: "$customerId",
totalSpent: { $sum: "$amount" }
}},
{ $sort: { totalSpent: -1 } }
])$match: Filters documents; placing a selective$matchearly reduces downstream work and may permit index use.$project: Includes, excludes, renames, or computes fields, such as{ total: { $multiply: ["$price", "$quantity"] } }.$group: Forms groups using_idas the grouping key and applies accumulators such as$sum,$avg,$min,$max, and$push.$unwind: Emits one document per array element. A document withitems: ["A", "B"]becomes two pipeline documents.$sort,$skip, and$limit: Order and paginate results; deterministic pagination should sort by a stable, preferably unique key.$lookup: Performs a left outer join with another collection, while$facetruns multiple sub-pipelines over the same input.- Optimization: Early filtering, index-supported sorting, reduced projections, and controlled array expansion limit CPU, memory, and intermediate-document volume.
B. Real-world Aggregation Examples
Real-world pipelines combine filtering, grouping, joining, and computed fields to produce operational or analytical outputs.
- Sales reporting: Monthly paid-order revenue can be calculated from order dates and amounts.
db.orders.aggregate([
{ $match: { status: "PAID" } },
{ $group: {
_id: {
year: { $year: "$orderDate" },
month: { $month: "$orderDate" }
},
revenue: { $sum: "$amount" },
orderCount: { $sum: 1 }
}},
{ $sort: { "_id.year": 1, "_id.month": 1 } }
])- Symbol definitions:
_id.yearand_id.monthform the monthly group key;revenueis the sum ofamount;orderCountcounts grouped documents. - Customer analysis: Grouping by
$customerIdcan compute lifetime spending with$sumand average order value with$avg. - Inventory monitoring: A pipeline can compute
remaining = stock - reserved, then use$matchto return products below a reorder threshold. - Product popularity:
$unwind: "$items"separates order lines, after which$groupcan total quantities byitems.productId. - Joined reporting:
$lookupcan enrich order documents with customer records, followed by$projectto expose only customer name, order total, and date. - Dashboard output:
$facetcan produce totals, status counts, and recent transactions in one request, with each facet returning its own result array. - Practical constraint: Large grouping or sorting stages may require substantial memory; indexes, bounded date ranges, and
allowDiskUseshould be considered for production workloads.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →