MongoDB Map Reduce

Map-Reduce — это вычислительная модель. Проще говоря, она разбивает большие объёмы работы (данных) на более мелкие задачи (MAP) для выполнения, а затем объединяет результаты в итоговый результат (REDUCE).

Map-Reduce в MongoDB очень гибок и вполне практичен для крупномасштабного анализа данных.

Команды MapReduce

Ниже приведён базовый синтаксис MapReduce:

>db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: collection,
      query: document,
      sort: document,
      limit: number
   }
)

Чтобы использовать MapReduce, нужно реализовать две функции: функцию Map и функцию Reduce. Функция Map вызывает emit(key, value), перебирает все записи в коллекции и передаёт ключ и значение в функцию Reduce для обработки.

Функция Map должна вызывать emit(key, value), чтобы возвращать пары ключ-значение.

Описание параметра:

  • map: функция сопоставления (генерирует последовательность пар ключ-значение, используемую в качестве параметра функции reduce).
  • reduceФункция reduce. Задача функции reduce — превратить пары ключ-значение в пару ключ-значение, то есть преобразовать массив значений в одно значение.
  • outКоллекция, в которой хранятся результаты статистики (если не указана, используется временная коллекция, которая автоматически удаляется после отключения клиента).
  • queryУсловие фильтрации; только документы, удовлетворяющие условию, будут вызывать функцию map. (query, limit, sort можно свободно комбинировать)
  • sortПараметр sort в сочетании с limit (также сортирует документы перед отправкой их в функцию map), что может оптимизировать механизм группировки.
  • limitВерхний предел количества документов, отправляемых в функцию map (без limit использование только sort не очень полезно).

В следующем примере выполняется поиск данных со status:"A" в коллекции orders, группировка по cust_id и вычисление суммы amount.


Использование MapReduce

Рассмотрим следующую структуру документа для хранения статей пользователя. В документе хранится user_name пользователя и поле status статьи:

>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "example",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "example",
   "status":"disabled"
})
WriteResult({ "nInserted" : 1 })
>db.posts.insert({
   "post_text": "Example,最全的技术文档。",
   "user_name": "example",
   "status":"active"
})
WriteResult({ "nInserted" : 1 })

Теперь мы используем функцию mapReduce для коллекции posts, чтобы выбрать опубликованные статьи (status:"active"), сгруппировать по user_name и вычислить количество статей для каждого пользователя:

>db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
)

Вывод вышеуказанного mapReduce:

{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}

Результат показывает, что есть 5 документов, соответствующих условию запроса (status:"active"), в функции map было сгенерировано 5 документов с парами ключ-значение, и в итоге функция reduce сгруппировала одинаковые значения ключей в 2 группы.

Описание параметров:

  • result: имя коллекции, в которой хранятся результаты. Это временная коллекция, которая автоматически удаляется после закрытия соединения MapReduce.
  • timeMillis: время выполнения в миллисекундах
  • input: количество документов, которые удовлетворяют условию и передаются в функцию map
  • emit: количество вызовов emit в функции map, т. е. общий объём данных во всех коллекциях
  • output: количество документов в коллекции результатов(count очень помогает при отладке)
  • ok: успешно ли выполнено, 1 означает успех
  • err: если произошла ошибка, здесь можно найти причину сбоя. Однако, судя по опыту, причина довольно расплывчата и не очень полезна.

Используйте оператор find для просмотра результатов запроса mapReduce:

> var map=function() { emit(this.user_name,1); }
> var reduce=function(key, values) {return Array.sum(values)}
> var options={query:{status:"active"},out:"post_total"}
> db.posts.mapReduce(map,reduce,options)
{ "result" : "post_total", "ok" : 1 }
> db.post_total.find();

Приведённый выше запрос выводит следующие результаты:

{ "_id" : "mark", "value" : 4 }
{ "_id" : "example", "value" : 1 }

Аналогичным образом MapReduce можно использовать для построения больших сложных запросов агрегации.

Функции Map и Reduce можно реализовать с помощью JavaScript, что делает MapReduce очень гибким и мощным.

Другие расширения