$sort / $limit
$sort in the aggregation pipeline. Index-friendly placement, multi-key sorting, and the difference between sort-on-disk vs in-memory.
MongoDB — $sort stage
EXAMPLE
// ===== Basic =====
db.orders.aggregate([
{ $sort: { created_at: -1 } }
]);
// -1 = descending, 1 = ascending
// ===== Multi-key =====
db.orders.aggregate([
{ $sort: { status: 1, total: -1 } }
]);
// First by status ascending, then by total descending (within same status).
// ===== Placement matters =====
// Put $sort EARLY when possible so it can use an index:
db.orders.aggregate([
{ $match: { customer_id: 'c-1' } },
{ $sort: { created_at: -1 } }, // can use index on { customer_id, created_at }
{ $limit: 20 }
]);
// Put $sort AFTER $project / $group when you sort on a computed field:
db.orders.aggregate([
{ $group: { _id: '$customer_id', total: { $sum: '$total' } } },
{ $sort: { total: -1 } },
{ $limit: 10 }
]);
// ===== Memory limit =====
// $sort uses 100MB of memory by default; beyond that:
db.orders.aggregate([
{ $sort: { foo: 1 } }
], { allowDiskUse: true }); // spill to disk if needed (slower)
// In MongoDB 6.0+, allowDiskUse defaults to true at the server level. Verify your setting.
// ===== Sort + limit optimisation =====
// $sort + $limit is special-cased: the server only needs to track the top N.
// Place $limit right after $sort to enable it:
{ $sort: ... }, { $limit: 10 } // optimised top-N
{ $sort: ... }, { $skip: 0 }, { $limit: 10 } // also optimised
{ $sort: ... }, { $project: ... }, { $limit: 10 } // STILL N sort all, then limit (worse)
// ===== Stable sort =====
// Mongo sort is NOT stable across equal keys; add a tiebreaker:
{ $sort: { created_at: -1, _id: -1 } }
// ===== Sort by metadata (text search) =====
db.posts.aggregate([
{ $match: { $text: { $search: 'mongo' } } },
{ $sort: { score: { $meta: 'textScore' } } }
]);
// ===== Index for sort =====
// Sort key MUST match a prefix of an index for the planner to use it without sorting.
db.orders.createIndex({ customer_id: 1, created_at: -1 });
// Then this query is index-supported:
db.orders.find({ customer_id: 'c-1' }).sort({ created_at: -1 }).limit(20);
// And the equivalent aggregate.
// ===== Patterns to internalise =====
// - $sort right after $match for index use
// - $limit immediately after $sort for top-N optimisation
// - Tiebreaker on _id for stability
// - Composite indexes whose suffix matches the sort direction(s)
// ===== Pitfalls =====
// - Sorting without an index on large collections -> in-memory + 100MB limit
// - allowDiskUse on hot queries -> very slow
// - Sort key with mixed asc/desc that breaks index order
// - Forgetting that text-search sort requires the meta { textScore }
Why it matters
$sort is index-friendly when it directly follows $match and matches an index prefix. Pair with $limit for top-N optimisation, add a tiebreaker on _id for stability, and watch memory limits on large pipelines. Place it early when possible, late when sorting on computed fields.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…