MongoDB Map Reduce

Map-Reduceは計算モデルであり、簡単に言うと、大量の作業(データ)を分解(MAP)して実行し、その後、結果を最終結果(REDUCE)にマージするものです。

MongoDBが提供するMap-Reduceは非常に柔軟で、大規模なデータ分析にもかなり実用的です。

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関数の2つの関数を実装する必要があります。Map関数はemit(key, value)を呼び出し、collection内のすべてのレコードを走査して、keyとvalueをReduce関数に渡して処理します。

Map関数はemit(key, value)を呼び出してキーと値のペアを返す必要があります。

パラメータの説明:

  • map:マッピング関数(キーと値のペアのシーケンスを生成し、reduce関数のパラメータとします)。
  • reduce統計関数。reduce関数のタスクは、key-valuesをkey-valueに変換すること、つまりvalues配列を単一の値valueに変換することです。
  • out統計結果を格納するコレクション(指定しない場合は一時コレクションが使用され、クライアントの切断後に自動的に削除されます)。
  • queryフィルタ条件。条件を満たすドキュメントのみがmap関数を呼び出します(query、limit、sortは自由に組み合わせられます)。
  • sortlimitと組み合わせるsortソートパラメータ(map関数に送信する前にドキュメントをソートします)。グループ化の仕組みを最適化できます。
  • limitmap関数に送信されるドキュメント数の上限(limitがない場合、sortを単独で使用してもあまり役に立ちません)。

以下の例では、コレクションorders内でstatus:"A"のデータを検索し、cust_idに基づいてグループ化し、amountの合計を計算します。


MapReduce を使用

次のドキュメント構造は、ユーザーの記事を格納するものです。ドキュメントにはユーザーのuser_nameと記事のstatusフィールドが格納されています:

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

次に、postsコレクションでmapReduce関数を使用して、公開済みの記事(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
}

結果は、クエリ条件(status:"active")を満たすドキュメントが合計5件あり、map関数で5つのキーと値のペアのドキュメントが生成され、最後にreduce関数で同じキーと値が2つのグループに分けられたことを示しています。

具体的なパラメータの説明:

  • result:結果を格納するcollectionの名前。これは一時コレクションであり、MapReduceの接続が閉じられると自動的に削除されます。
  • timeMillis:実行にかかった時間(ミリ秒単位)
  • input:条件を満たしてmap関数に送信されたドキュメントの数
  • emit:map関数内でemitが呼び出された回数、つまりすべてのコレクション内のデータ総量
  • 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" : "サンプル", "value" : 1 }

同様の方法で、MapReduceを使用して大規模で複雑な集計クエリを構築できます。

Map関数とReduce関数はJavaScriptを使用して実装できるため、MapReduceの使用は非常に柔軟で強力です。

その他の拡張