
MongoDB Aggregation Pipelines: The Performance Patterns You Need
Why Aggregation Pipelines Go Wrong
MongoDB aggregation pipelines are deceptively easy to write and surprisingly easy to write badly. The document-oriented mental model that works well for simple queries leads developers astray when building complex analytics. The result: aggregations that work fine on a 50K document collection and time out on a 5M document production dataset.
These are the patterns that distinguish fast aggregations from slow ones.
Pattern 1: $match Early, $project Early
The most common aggregation performance mistake: not putting $match as the first stage. When $match is not first, MongoDB scans every document in the collection for subsequent stages. Put $match first to reduce the working set before any expensive operations. Similarly, use $project early to eliminate fields you do not need — reducing memory pressure across all subsequent stages.
Pattern 2: Index Eligibility for $match
Your $match stage only leverages indexes if the matched fields have indexes and the $match is in the first pipeline stage. Use explain("executionStats") on your aggregation and look at the executionStages output — if you see COLLSCAN instead of IXSCAN, you are not using an index and need to add one or restructure the query.
Pattern 3: $lookup Is Expensive — Design Around It
$lookup performs a join operation that is expensive at scale. Before using $lookup, ask: can this relationship be modelled as embedding instead? If the joined data is small and infrequently updated, embedding is almost always faster. When $lookup is necessary, add a $match stage after it to reduce the output size immediately, and ensure the foreign key field has an index in the joined collection.