Troubleshoot the Reduce Function
On this page
Note
Aggregation Pipeline as Alternative to Map-Reduce
Starting in MongoDB 5.0, map-reduce is deprecated:
Instead of map-reduce, you should use an aggregation pipeline. Aggregation pipelines provide better performance and usability than map-reduce.
You can rewrite map-reduce operations using aggregation pipeline stages, such as
$group
,$merge
, and others.For map-reduce operations that require custom functionality, you can use the
$accumulator
and$function
aggregation operators. You can use those operators to define custom aggregation expressions in JavaScript.
For examples of aggregation pipeline alternatives to map-reduce, see:
An aggregation pipeline is also easier to troubleshoot than a map-reduce operation.
The reduce
function is a JavaScript function that "reduces" to a
single object all the values associated with a particular key during a
map-reduce operation. The reduce
function
must meet various requirements. This tutorial helps verify that the
reduce
function meets the following criteria:
The
reduce
function must return an object whose type must be identical to the type of thevalue
emitted by themap
function.The order of the elements in the
valuesArray
should not affect the output of thereduce
function.The
reduce
function must be idempotent.
For a list of all the requirements for the reduce
function, see
mapReduce
, or mongosh
helper method
db.collection.mapReduce()
.
Confirm Output Type
You can test that the reduce
function returns a value that is the
same type as the value emitted from the map
function.
Define a
reduceFunction1
function that takes the argumentskeyCustId
andvaluesPrices
.valuesPrices
is an array of integers:var reduceFunction1 = function(keyCustId, valuesPrices) { return Array.sum(valuesPrices); }; Define a sample array of integers:
var myTestValues = [ 5, 5, 10 ]; Invoke the
reduceFunction1
withmyTestValues
:reduceFunction1('myKey', myTestValues); Verify the
reduceFunction1
returned an integer:20 Define a
reduceFunction2
function that takes the argumentskeySKU
andvaluesCountObjects
.valuesCountObjects
is an array of documents that contain two fieldscount
andqty
:var reduceFunction2 = function(keySKU, valuesCountObjects) { reducedValue = { count: 0, qty: 0 }; for (var idx = 0; idx < valuesCountObjects.length; idx++) { reducedValue.count += valuesCountObjects[idx].count; reducedValue.qty += valuesCountObjects[idx].qty; } return reducedValue; }; Define a sample array of documents:
var myTestObjects = [ { count: 1, qty: 5 }, { count: 2, qty: 10 }, { count: 3, qty: 15 } ]; Invoke the
reduceFunction2
withmyTestObjects
:reduceFunction2('myKey', myTestObjects); Verify the
reduceFunction2
returned a document with exactly thecount
and theqty
field:{ "count" : 6, "qty" : 30 }
Ensure Insensitivity to the Order of Mapped Values
The reduce
function takes a key
and a values
array as its
argument. You can test that the result of the reduce
function does
not depend on the order of the elements in the values
array.
Define a sample
values1
array and a samplevalues2
array that only differ in the order of the array elements:var values1 = [ { count: 1, qty: 5 }, { count: 2, qty: 10 }, { count: 3, qty: 15 } ]; var values2 = [ { count: 3, qty: 15 }, { count: 1, qty: 5 }, { count: 2, qty: 10 } ]; Define a
reduceFunction2
function that takes the argumentskeySKU
andvaluesCountObjects
.valuesCountObjects
is an array of documents that contain two fieldscount
andqty
:var reduceFunction2 = function(keySKU, valuesCountObjects) { reducedValue = { count: 0, qty: 0 }; for (var idx = 0; idx < valuesCountObjects.length; idx++) { reducedValue.count += valuesCountObjects[idx].count; reducedValue.qty += valuesCountObjects[idx].qty; } return reducedValue; }; Invoke the
reduceFunction2
first withvalues1
and then withvalues2
:reduceFunction2('myKey', values1); reduceFunction2('myKey', values2); Verify the
reduceFunction2
returned the same result:{ "count" : 6, "qty" : 30 }
Ensure Reduce Function Idempotence
Because the map-reduce operation may call a reduce
multiple times
for the same key, and won't call a reduce
for single instances
of a key in the working set, the reduce
function must return a value of the
same type as the value emitted from the map
function. You can test
that the reduce
function process "reduced" values without
affecting the final value.
Define a
reduceFunction2
function that takes the argumentskeySKU
andvaluesCountObjects
.valuesCountObjects
is an array of documents that contain two fieldscount
andqty
:var reduceFunction2 = function(keySKU, valuesCountObjects) { reducedValue = { count: 0, qty: 0 }; for (var idx = 0; idx < valuesCountObjects.length; idx++) { reducedValue.count += valuesCountObjects[idx].count; reducedValue.qty += valuesCountObjects[idx].qty; } return reducedValue; }; Define a sample key:
var myKey = 'myKey'; Define a sample
valuesIdempotent
array that contains an element that is a call to thereduceFunction2
function:var valuesIdempotent = [ { count: 1, qty: 5 }, { count: 2, qty: 10 }, reduceFunction2(myKey, [ { count:3, qty: 15 } ] ) ]; Define a sample
values1
array that combines the values passed toreduceFunction2
:var values1 = [ { count: 1, qty: 5 }, { count: 2, qty: 10 }, { count: 3, qty: 15 } ]; Invoke the
reduceFunction2
first withmyKey
andvaluesIdempotent
and then withmyKey
andvalues1
:reduceFunction2(myKey, valuesIdempotent); reduceFunction2(myKey, values1); Verify the
reduceFunction2
returned the same result:{ "count" : 6, "qty" : 30 }