Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-62940

DocumentSourceGroup returns different results for in-memory group and spilled group when using $sum

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Major - P3 Major - P3
    • 6.0.0-rc0
    • Affects Version/s: 5.2.0, 4.2.17, 5.0.5, 5.1.1, 4.4.12
    • Component/s: None
    • Fully Compatible
    • ALL
    • Hide
      // Inserts 1000 groups each of which has 4 documents.
      for (let i = 0; i < 1000; ++i) {
      	db.spilling.insert([
      		{k: i, n: 1e+34},
      		{k: i, n: NumberDecimal("0.1")},
      		{k: i, n: NumberDecimal("0.01")},
      		{k: i, n: -1e+34}]);
      }
      
      // Turns on the classical engine.
      db.adminCommand({setParameter: 1, internalQueryForceClassicEngine: true});
      // Has the document source group spill.
      db.adminCommand({setParameter: 1, internalDocumentSourceGroupMaxMemoryBytes: 1000});
      // Makes sure that the document source group will spill.
      db.spilling.aggregate([{$group: {_id: "$k", o: {$sum: "$n"}}}, {$group: {_id: "$o"}}], {allowDiskUse: false});
      Error: command failed: {
      	"ok" : 0,
      	"errmsg" : "PlanExecutor error during aggregation :: caused by :: Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse:true to opt in.",
      	"code" : 292,
      	"codeName" : "QueryExceededMemoryLimitNoDiskUseAllowed"
      
      // Two different results.
      db.spilling.aggregate([{$group: {_id: "$k", o: {$sum: "$n"}}}, {$group: {_id: "$o"}}], {allowDiskUse: true}).toArray();
      [ { "_id" : NumberDecimal("0.11") }, { "_id" : NumberDecimal("0") } ]
      // Has the document source group not spill.
      db.adminCommand({setParameter: 1, internalDocumentSourceGroupMaxMemoryBytes: 100000000});
      { "was" : NumberLong(1000), "ok" : 1 }
      // One result as expected.
      db.spilling.aggregate([{$group: {_id: "$k", o: {$sum: "$n"}}}, {$group: {_id: "$o"}}], {allowDiskUse: true}).toArray();
      [ { "_id" : NumberDecimal("0.11") } ]
      
      Show
      // Inserts 1000 groups each of which has 4 documents. for (let i = 0; i < 1000; ++i) { db.spilling.insert([ {k: i, n: 1e+34}, {k: i, n: NumberDecimal( "0.1" )}, {k: i, n: NumberDecimal( "0.01" )}, {k: i, n: -1e+34}]); } // Turns on the classical engine. db.adminCommand({setParameter: 1, internalQueryForceClassicEngine: true }); // Has the document source group spill. db.adminCommand({setParameter: 1, internalDocumentSourceGroupMaxMemoryBytes: 1000}); // Makes sure that the document source group will spill. db.spilling.aggregate([{$group: {_id: "$k" , o: {$sum: "$n" }}}, {$group: {_id: "$o" }}], {allowDiskUse: false }); Error: command failed: { "ok" : 0, "errmsg" : "PlanExecutor error during aggregation :: caused by :: Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse: true to opt in." , "code" : 292, "codeName" : "QueryExceededMemoryLimitNoDiskUseAllowed" // Two different results. db.spilling.aggregate([{$group: {_id: "$k" , o: {$sum: "$n" }}}, {$group: {_id: "$o" }}], {allowDiskUse: true }).toArray(); [ { "_id" : NumberDecimal( "0.11" ) }, { "_id" : NumberDecimal( "0" ) } ] // Has the document source group not spill. db.adminCommand({setParameter: 1, internalDocumentSourceGroupMaxMemoryBytes: 100000000}); { "was" : NumberLong(1000), "ok" : 1 } // One result as expected. db.spilling.aggregate([{$group: {_id: "$k" , o: {$sum: "$n" }}}, {$group: {_id: "$o" }}], {allowDiskUse: true }).toArray(); [ { "_id" : NumberDecimal( "0.11" ) } ]
    • QE 2022-02-07, QE 2022-02-21, QE 2022-03-07, QE 2022-03-21

      The reason why the document source group may return different results for in-memory group and spilled group is because when the document source group spills, it spills the accumulator state in the final state, not in intermediate state. For example, $sum accumulator stores decimal total and non-decimal total separately but when the document source group spills data, it calls AccumulatorState::getValue() which returns the final state == decimalTotal.add(nonDecimalTotal.getDecimal()).

      We may have potentially similar issues for accumulators that need intermediate state in sharded environment and it needs further investigation. I haven’t yet gone deeper on this aspect. But I’m not sure what’s MongoDB’s guarantee about aggregation results in sharded environment.
      If we had similar issues for sharded envionment, those issues would exist for both the classic engine and the SBE. To fix the sharded environment issues (assuming there are), we need to send the full intermediate accumulator state over the wire. Currently, we’re sending semi-processed intermediate accumulator state over the wire.

            Assignee:
            yoonsoo.kim@mongodb.com Yoon Soo Kim
            Reporter:
            yoonsoo.kim@mongodb.com Yoon Soo Kim
            Votes:
            0 Vote for this issue
            Watchers:
            9 Start watching this issue

              Created:
              Updated:
              Resolved: