#javascript #mongodb #map #mapreduce #reduce
#javascript #mongodb #словарь #mapreduce #уменьшить
Вопрос:
Я пытаюсь выполнить простое уменьшение карты в оболочке Mongo, но функция уменьшения никогда не вызывается. Это мой код :
db.sellers.mapReduce(
function(){ emit( this._id, 'Map') } ,
function(k,vs){ return 'Reduce' },
{ out: { inline: 1}})
И в результате получается
{
"results" : [
{
"_id" : ObjectId("4da0bdb56bd728c276911e1a"),
"value" : "Map"
},
{
"_id" : ObjectId("4da0df9a6bd728c276911e1b"),
"value" : "Map"
}
],
"timeMillis" : 0,
"counts" : {
"input" : 2,
"emit" : 2,
"output" : 2
},
"ok" : 1,
}
Что не так?
Я использую 32-разрядную версию MongoDB 1.8.1 в Ubuntu 10.10
Ответ №1:
Цель reduce
состоит в том, чтобы, ekhem, свести набор значений, связанных с данным ключом, к одному значению (агрегировать результаты). Если вы выдаете только одно значение для каждого ключа MapReduce, в reduce нет необходимости, вся работа выполнена. Но если вы создадите две пары для данного _id
, будет вызвано сокращение:
emit(this._id, 'Map1');
emit(this._id, 'Map2');
это вызовет reduce со следующими параметрами:
reduce(_id, ['Map1', 'Map2'])
Скорее всего, вы захотите использовать _id
для MapReduce ключ при фильтрации набора данных: emit
только тогда, когда данная запись удовлетворяет некоторому условию. Но опять же, reduce
в этом случае не будет вызван, что и ожидалось.
Комментарии:
1. Ты прав, Томаш. Я выбрал этот пример из документов MongoDB. Это очень помогло бы отображать / уменьшать новичков, подобных мне, если бы это было четко упомянуто. Скромный «зеленый чек» для вас!
2. Все еще можно сделать вывод, что reduce получит, используя ваш пример, reduce(_id, [‘Map1’]) в случае отправки только 1 элемента. Отсутствие прохождения через reduce нарушает мой результирующий набор.
3. Я думаю, что не вызывать reduce для отдельных значений — странный выбор реализации. Таким образом, каждый раз, когда вы меняете структуру результата,
map
вам также приходится корректироватьreduce
, чтобы получать единообразные результаты в случае результатов с одним значениемmap
.4. Да, это настоящая боль. Я собираю большой набор данных, и это в основном означает, что я не могу агрегировать наборы, которые содержат только один объект. Странный выбор реализации…
Ответ №2:
Ну, MongoDB не вызывает функцию уменьшения для ключа, если для нее есть только одно значение.
На мой взгляд, это плохо. Решение о том, пропускать ли единственное значение или выполнить с ним какую-либо операцию, должно быть предоставлено моему коду редуктора.
Теперь, если мне нужно выполнить какую-либо операцию над единственным значением, я заканчиваю тем, что пишу функцию finalize и в finalize пытаюсь определить, какое значение прошло через редуктор, а какое нет.
Я очень уверен, что в случае Hadoop этого не происходит.
Комментарии:
1. Спасибо! это очень нелогично, часть заданий reducers заключается в создании элемента определенным образом, который вполне может отличаться.
Ответ №3:
Map reduce соберет значения с общим ключом в одно значение.
В этом случае ничего не нужно делать, потому что каждое значение, передаваемое map, имеет другой ключ. Сокращение не требуется.
db.sellers.mapReduce(
function(){ emit( this._id, 'Map') } ,
function(k,vs){ return 'Reduce' },
{ out: { inline: 1}})
Это не совсем понятно из чтения документации.
Если вы хотите вызвать сокращение, вы могли бы жестко указать идентификатор, подобный этому:
db.sellers.mapReduce(
function(){ emit( 1, 'Map') } ,
function(k,vs){ return 'Reduce' },
{ out: { inline: 1}})
Теперь все значения, передаваемые map, будут уменьшаться до тех пор, пока не останется только одно.
Ответ №4:
Следует также упомянуть, что, согласно документации, «MongoDB может вызывать функцию уменьшения более одного раза для одного и того же ключа. В этом случае предыдущий вывод функции уменьшения для этого ключа станет одним из входных значений для следующего вызова функции уменьшения для этого ключа.».
Кроме того, reduce
должно быть ассоциативным, коммутативным и идемпотентным:
reduce(key, [ C, reduce(key, [ A, B ]) ] ) == reduce( key, [ C, A, B ] )
reduce( key, [ reduce(key, valuesArray) ] ) == reduce( key, valuesArray )
reduce( key, [ A, B ] ) == reduce( key, [ B, A ] )
Итак, это означает, что reduce
функция должна быть готова к приему объекта, который является результатом предыдущего вызова самой себя. Что (по крайней мере, лично для меня) означает, что лучший способ реализации mapReduce
— заставить map
функцию (если это возможно) выдавать значения в том же формате, что и возвращаемая reduce
функция. Тогда reduce
функция может быть реализована для поддержки только одного формата ввода. И, в результате, даже если существует только один объект, выданный map
(и в результате вызов reduce
пропущен), в конечном результате mapReduce
значение для ключей, для которых reduce
никогда не вызывался, все равно будет в том же формате, что и значение для остальных ключей.
Например, если у нас есть следующая структура документа:
{
"foo": <some_string>,
"status": ("foo"|"bar")
}
map
функция может быть следующей:
function() {
var value = {
"num_total": 1,
"num_foos": 0,
"num_bars": 0
};
if (this.status == "foo") {
value["num_foos"] = 1;
}
if (this.status == "bar") {
value["num_bars"] = 1;
}
emit(this.foo, value);
}
и reduce
функция будет:
function(key, values) {
var reduced = {
"num_total": 0,
"num_foos": 0,
"num_bars": 0
};
values.forEach(function(val) {
reduced["num_total"] = val["num_total"];
reduced["num_foos"] = val["num_foos"];
reduced["num_bars"] = val["num_bars"];
});
return reduced;
}