Я хочу ещё раз закрепить для себя как пользоваться map-reduce в MongoDB. Закреплять буду на примере домашнего задания 3.4 из курса для MongoDB for DBAs. Текст задания в оригинале:

Use the built-in Map/Reduce functionality in MongoDB to answer the following question: of the zip codes in Pennsylvania, how many are closer to Pittsburgh and how many are closer to Philadelphia? The map function is already written for you. Question: How many zip codes in PA are closer to philadelphia than to pittsburgh (given our map function’s implementation)? Use map/reduce to find the answer.

Текст задания по-русски:

Есть коллекция с документами такого вида:

{
    "city" : "ACMAR",
    "loc" : [
        -86.51557,
        33.584132
    ],
    "pop" : 6055,
    "state" : "AL",
    "_id" : "35004"
}

Необходимо подсчитать количество городов в штате Пенсильвания, которые расположены ближе к Филадельфии, чем к Питсбургу. Даны координаты городов Питсбурга и Филадельфии:

Питсбург: [-80.064879, 40.612044];

Филадельфия: [-74.978052, 40.089738];

Предполагается что для решения задачи будет использоваться map/reduceЧто такое map-reduce? В mongo это всего-лишь один из способов агрегирования данных. Не могу точно сказать как это работает, могу только строить предположения. В тексте задания так же представлена функция map:

function map_closest() {
    var pitt = [-80.064879, 40.612044];
    var phil = [-74.978052, 40.089738];

    function distance(a, b) {
        var dx = a[0] - b[0];
        var dy = a[1] - b[1];
        return Math.sqrt(dx * dx + dy * dy);
    }

    if (distance(this.loc, pitt) < distance(this.loc, phil)) {
        emit("pitt",1);
    } else {
        emit("phil",1);
    }
}

По замыслу в этой функции данные должны разбиваться на группы и группироваться по некоторому ключу, чтобы потом подсчитывать в функции reduce. Функция map будет вызвана для всех элементов коллекции.

Забегу вперёд и сразу покажу как это выглядит в оболочке mongo вместе с ответом:

db.zips.mapReduce(map_closest, reduce_closest, {query: {state: 'PA'}, out: {inline: 1}})
{
    "results" : [
        {
            "_id" : "phil",
            "value" : 732
        },
        {
            "_id" : "pitt",
            "value" : 726
        }
    ],
    "timeMillis" : 62,
    "counts" : {
        "input" : 1458,
        "emit" : 1458,
        "reduce" : 19,
        "output" : 2
    },
    "ok" : 1,
}

Опция out необходима для указания коллекции в котороую будет записан результат. Как я понял это обязательно начиная с версии 1.8. Я указал что хочу видеть результат на экране. Если забыть это сделать, то получится ошибка вроде этой:

assert failed : need to supply an optionsOrOutString

или этой:

“errmsg” : “exception: ‘out’ has to be a string or an object”

Опция query фильтрует набор данных до того, как к ним будет применена функция map. Я отобрал здесь записи со штатом PA(Филадельфия).

Как работает map reduce в mongodb?

Для всех лементов коллекции вызывается функция map. На этом шаге данные разбиваются и группируются по ключу. Функция map должна вызывать метод emit. Аргументами метода emit должны быть некие ключ и некоторое значение. В рассматриваемой задачи ключём является название города, а значение всегда будет равным единице. Вызов функции emit(“phil”, 1) для города означает, что он ближе к Филадельфии нежели к Питсбургу.

Далее вероятно формируются объекты в виде ключа и списка значений. В нашей задаче они должны выглядеть как то так:

{phil: [1, 1, 1, 1, 1]}
{phit: [1, 1]}

Можно предположить что при каждом вызове функции emit с ключём и значением – это значение добавляется в список значений для этого ключа. Вызов emit(‘phil’, 1) – добавит элемент 1 в список к ключу phil. Для phit тоже самое.

На шаге reduce необходимо просуммировать сгруппированные на шаге map данные. Для этого я создал вот такую простую функцию reduce:

function reduce_closest(key, values) { 
    var result = 0;
    for(var i = 0; i < values.length; i++)
    {
        result += values[i];
    }
    return result;
}

Как я понял, в функции reduce обязательно нужно возвращать данные в том-же виде в каком они подаются ей на вход, имеется ввиду value(элемент списка values). То есть если элементы списка values – целые числа. возвращать надо целое число, если это объекты – возвращает надо такой же объект. Данные с выхода функции reduce попадают на вход другой функции reduce и так далее по цепочке.

В первый раз я прозевал этот момент и реализовал функцию reduce в одну строчку: return values.length. Это оказалось не верно. Потом я ещё раз пересмотре лекцию и понял что это было ошибочным решением.

Комментарии

comments powered by Disqus