Download

Exceeded memory limit for $group, but didn't allow external sort

A $group stage needed more than 100 MB of memory, and the aggregation wasn’t allowed to write temporary files. Filter before grouping, keep less in each group (especially with $push), or allow disk use (Atlas free and Flex clusters can’t).

MongoDB error 292· Tested on MongoDB 8.0.32 (mongosh 2.12.0)· Updated 11 October 2026

PlanExecutor error during aggregation :: caused by :: Exceeded memory limit for $group, but didn't allow external spilling; pass allowDiskUse:true to opt in

What it means

$group keeps every group, and everything it accumulates for each one, in memory until it has seen the last input document. Each pipeline stage may use 100 MB (104,857,600 bytes). Past that, $group can write temporary files to disk, but only if the aggregation allows disk use. This one didn’t, so the server stopped it with code 292, QueryExceededMemoryLimitNoDiskUseAllowed.

MongoDB 8.0 says “didn’t allow external spilling; pass allowDiskUse:true to opt in”. Older servers said “didn’t allow external sort. Pass allowDiskUse:true to opt in.” Same error, same fixes.

Since MongoDB 6.0, disk use is allowed by default (allowDiskUseByDefault is true). So on a self-hosted server you usually meet this error only when something turns it off, or on an older server. On Atlas free and Flex clusters, Atlas ignores allowDiskUse and behaves as if it were false.

Common causes

  1. $push: "$$ROOT" or a large $push/$addToSet. Collecting whole documents into each group holds most of the collection in memory at once.
  2. Grouping before filtering. The $match that would cut the input comes after $group, or not at all.
  3. Disk use turned off: { allowDiskUse: false } on the aggregation, allowDiskUseByDefault set to false, or a server older than 6.0 where allowDiskUse: true wasn’t passed.
  4. An Atlas free or Flex cluster, which never writes temporary files.
  5. A high-cardinality group key (a user id, a timestamp) with large accumulators per group.

How to fix it

Filter and trim before $group

Put $match first so $group sees fewer documents (an index on the matched fields helps), and $project away fields you don’t need:

db.orders.aggregate([
  { $match: { placedAt: { $gte: ISODate("2026-12-01") } } },
  { $group: { _id: "$customer", orders: { $push: { id: "$_id", total: "$total" } } } }
])

Accumulate less

Counting and summing take a few bytes per group; pushing documents takes their whole size. If you only need totals, use $sum, $avg, $min, $max or $count. If you need a few documents per group, $topN, $bottomN, $firstN and $lastN (MongoDB 5.2 and later) keep only n of them:

{ $group: {
  _id: "$customer",
  orders: { $sum: 1 },
  spent: { $sum: "$total" },
  latest: { $topN: { n: 3, sortBy: { placedAt: -1 }, output: "$_id" } }
} }

Allow disk use

On a self-hosted server or a dedicated Atlas cluster:

db.orders.aggregate(pipeline, { allowDiskUse: true })

Check the server’s default with db.adminCommand({ getParameter: 1, allowDiskUseByDefault: 1 }). With Mongoose, Model.aggregate(pipeline).allowDiskUse(true). Spilling is slower than staying in memory, so trim the pipeline first.

Watch the 16 MB document limit too

Even with disk use, each group’s result is one document and can’t exceed 16 MB. A $push that gathers too much fails with BSONObjectTooLarge instead.

Reproduce it

A temporary MongoDB 8.0.32 container of our own (mongo:8.0, removed afterwards), with 150,000 orders of about 1 KB each (161 MB) for 50,000 customers; internalDocumentSourceGroupMaxMemoryBytes at its default, 104857600.

db.orders.aggregate(
  [{ $group: { _id: "$customer", orders: { $push: "$$ROOT" } } }],
  { allowDiskUse: false }
)
MongoServerError[QueryExceededMemoryLimitNoDiskUseAllowed]: PlanExecutor error during aggregation :: caused by :: Exceeded memory limit for $group, but didn't allow external spilling; pass allowDiskUse:true to opt in

Each of these returned with allowDiskUse: false still set:

  • $sum of the count and the totals instead of $push: 50,000 groups;
  • $push of only _id and total: 50,000 groups in 2.2 seconds;
  • a $match on one month (indexed) before the $group with $push: "$$ROOT": 8,921 groups in 0.2 seconds.

With disk use left at the default, the original pipeline returned 50,000 groups in 3.4 seconds, and explain("executionStats") showed usedDisk: true and spills: 3.

In Inlet

Queries use mongosh syntax, so you can paste the pipeline and run it as db.orders.aggregate([…], { allowDiskUse: true }), then trim it until it fits. A refused aggregation shows the server’s message.

Inlet: a database client for the Mac

One native app for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and Redis. It explains errors where they happen, holds your edits until you save them, and keeps production read-only until you say so.

Version 0.1.0 · macOS 26 Tahoe or later · Apple silicon and Intel