MongoDBでゆるふわDB体験

第10回MongoDBでの集計処理

MongoDBでの集計処理の概要

一般的なNoSQLプロダクトは、RDBのSQLでいうGroup By句やSum関数などの集計機能がありません。集計を実施するには、アプリケーション側で独自にコードを書くことになります。

しかし、MongoDBは、NoSQLのパフォーマンスを維持しながら、RDBライクな機能を実装することを開発方針として掲げており、集計機能に関してもいち早く実装してきました。MongoDBで集計処理を行う方法は3つあります。

1. Aggregationフレームワーク
SQLでいうGroup By句やSum関数を提供します。Mongo Shellからクエリと同じように実施できます。一部の処理($groupと$sort)はシャーディングに対応しており、各シャードで処理します。
2. MongoDBのMap/Reduce機能
Map関数/Reduce関数を独自に定義し、集計処理を行います。Aggregationフレームワークではできないような、複雑な集計処理を行うために使用します。シャーディングに対応していますので、分散処理を実施することが可能です。
3. その他集計処理ミドルとの連携
より大規模に集計処理を行うため、他の集計処理ミドルとの連携も可能です。今回の記事では、Hadoopとの連携を紹介します。

それではさっそくAggregationフレームワークを使って、集計処理を実施してみましょう。

Aggregationフレームワーク

データの準備

最初に集計するためのデータを準備しましょう。今回はWebサーバーのアクセスログを想定したデータを用います。下記のサンプルデータをMongoDBへ保存します。

> db.httplogs.insert({"url_path":"/", "status":"200"});
> db.httplogs.insert({"url_path":"/", "status":"200"});
> db.httplogs.insert({"url_path":"/", "status":"500"});
> db.httplogs.insert({"url_path":"/", "status":"500"});
> db.httplogs.insert({"url_path":"/top", "status":"200"});
> db.httplogs.insert({"url_path":"/top", "status":"200"});
> db.httplogs.insert({"url_path":"/top", "status":"404"});
> db.httplogs.insert({"url_path":"/user", "status":"200"});
> db.httplogs.insert({"url_path":"/user", "status":"500"});
> db.httplogs.insert({"url_path":"/user", "status":"500"});

Aggregationフレームワークで集計する

それではAggregationフレームワークを使って集計してみましょう。先ほど保存したアクセスログのデータ使用して、まずはURLごとのアクセス数を集計しましょう。ここではHTTPステータスコードは無視します。HTTPステータスコードも利用した複数キーの集計は、後ほど紹介します。

URLごとのアクセス数の集計をAggregationフレームワークで実施するには、下記のコードを実行します。

> db.httplogs.aggregate (
  { $group   : { "_id"  : "$url_path",
                 "count" : { "$sum" : 1 } } }
);

実行してみます。

> db.httplogs.aggregate (
  { $group   : { "_id"  : "$url_path", 
                 "count" : { "$sum" : 1 } } }
);
{
        "result" : [
                {
                        "_id" : "/user",
                        "count" : 3
                },
                {
                        "_id" : "/top",
                        "count" : 3
                },
                {
                        "_id" : "/",
                        "count" : 4
                }
        ],
        "ok" : 1
}

集計できました。

複数キーで集計する

次はURLとHTTPステータスコードの2つをキーとして集計してみましょう。 複数キーで集計するには、$groupオペレータの"_id"の値を下記のように修正します。

> db.httplogs.aggregate(
  { $group   : { "_id"  : { "url_path" : "$url_path", 
                            "status" : "$status" },
                 "count" : { "$sum" : 1 } } }
);

実行してみます。

> db.httplogs.aggregate(
  { $group   : { "_id"  : { "url_path" : "$url_path", 
                            "status" : "$status" },
                 "count" : { "$sum" : 1 } } }
);
{
        "result" : [
                {
                        "_id" : {
                                "url_path" : "/user",
                                "status" : "500"
                        },
                        "count" : 2
                },
                {
                        "_id" : {
                                "url_path" : "/user",
                                "status" : "200"
                        },
                        "count" : 1
                },
...

こちらも集計できました。

Aggregationフレームワークでできること

このように、今回のような単純な集計なら、Aggregationフレームワークで実施することができます。

他にもAggregationフレームワークには集計に便利なオペレータが用意されており、組み合わせて使うことができます。

たとえば以下のような、テストの平均点を求めるようなSQLを考えてみます。

リスト1 テストの平均点を求めるSQL
SELECT name as '_id', AVG(score) as 'average' FROM scores
WHERE year = 'junior'
GROUP BY = name

上記のSQLをAggregationフレームワークで表現すると、このようになります。

リスト2 テストの平均点を求めるAggregationフレームワーク
db.scores.aggregate(
  { $match   : { "year" : "junior" } },
  { $project : { "name" : 1, "score" : 1 } },
  { $group   : { "_id"  : "$name",
                 "average" : { "$avg" : "$score" } } }
); 

このようにオペレータをフィルタのように使い、パイプライン処理を行うことでより高度な集計処理が可能となります図1参照⁠⁠。

図1 Aggregationフレームワークのパイプライン処理
図1 Aggregationフレームワークのパイプライン処理

その他のオペレータについては、公式マニュアルにSQLとAggregationフレームワークのマッピング表がありますので、一部紹介します。

SQLAggregationオペレータ
WHERE$match
GROUP BY$group
HAVING$match
SELECT$project
ORDER BY$sort
LIMIT$limit
SUM()$sum
COUNT()$sum

公式マニュアルには他にも、各SQLに対応したサンプルコードも掲載されています。

上記のオペレータで可能な範囲は、Aggregationフレームワークで集計できます。それ以上の複雑な処理を行う場合は、Map/Reduce機能を使ってMap関数とReduce関数を定義することで実施可能です。

それでは、次にMap/Reduceで集計してみましょう。

MongoDBのMap/Reduce

これまでにAggregationフレームワークで実行した集計を、今度はMap/Reduceを使って集計します。Map/ReduceにはJavaScriptで記述したMap関数とReduce関数が必要となりますので、準備しましょう。

Map関数の準備

Map関数は、おおまかに言うとReduce関数で使用するためのkeyとvalueを作成するための処理です。Map関数では集計処理はまだ行いません。

keyにはグルーピングするキーを指定します。先ほどと同様に、まずはURLごとのアクセス数を集計しますので、HTTPステータスコードは無視します。HTTPステータスコードも利用した複数キーの集計は、後ほど紹介します。

Map関数内部でkeyとvalueを作成するために、emit関数を使用します。第1引数にkey, 第2引数に集計方法を記述します。ここでは、第1引数this.url_path、第2引数{count : 1}を指定します。

> map = function() {
  emit(this.url_path, {count: 1});
}

Reduce関数の準備

Reduce関数には、Map関数内のemit関数で指定したkeyでグルーピングされた状態で、keyとvalueが渡ってきます。今回は、URLごとにアクセス数を数えるため、countを加算していく処理を記述します。

> reduce = function(key, values) {
  var count = 0;
  values.forEach(function(v) {
    count += v['count'];
  });
  return {count: count};
}

Map/Reduceで集計する

Map関数とReduce関数が作成できたら、集計してみましょう。集計には、集計するコレクションのmapReduce関数の引数に第1引数にMap関数、第2引数にReduce関数を渡します。第3引数はオプションを渡します。{out: {inline:1}}を指定することで、結果をコンソールに出力することができます。

それでは、mapReduce関数を実行してみましょう。

> db.httplogs.mapReduce( map, reduce, {out: {inline:1}} );
{
        "results" : [
                {
                        "_id" : "/",
                        "value" : {
                                "count" : 4
                        }
                },
                {
                        "_id" : "/top",
                        "value" : {
                                "count" : 3
                        }
                },
                {
                        "_id" : "/user",
                        "value" : {
                                "count" : 3
                        }
                }
        ],
        "timeMillis" : 1,
        "counts" : {
                "input" : 10,
                "emit" : 10,
                "reduce" : 3,
                "output" : 3
        },
        "ok" : 1,
}

URLアクセス数の合計が、countで集計されていることが確認できました。

次はURLとHTTPステータスコードの2つをキーとして集計してみましょう。

※)
オプションでは、結果を保存するコレクションの指定や、最後に実施するfinalize関数の指定が可能です。詳細は公式マニュアルを参照ください。

複数キーで集計する

複数キーで集計するためには、emit関数の第1引数にハッシュで指定します。Map関数を修正します。

URLとHTTPステータスコードをキーとするために、第1引数を下記のように変更します。

> map = function() {
  emit({url_path: this.url_path, status: this.status}, {count: 1});
}

Reduce関数は先ほどのままで良いので、mapReduce関数を実行してみましょう。

> db.httplogs.mapReduce( map, reduce, {out: {inline:1}} );
{
        "results" : [
                {
                        "_id" : {
                                "url_path" : "/",
                                "status" : "200"
                        },
                        "value" : {
                                "count" : 2
                        }
                },
                {
                        "_id" : {
                                "url_path" : "/",
                                "status" : "500"
                        },
                        "value" : {
                                "count" : 2
                        }
                },
...

URLとHTTPステータスコードが集計のキーとなっていることが確認できました。

Map/Reduceでできること

Map/Reduceでは、JavaScriptを用いて自由にMap関数とReduce関数を定義できることが大きな特徴です。Aggregationフレームワークではできない、ifなどの制御構文やその他のJavaScriptの関数を使用した処理を実装することが可能です。

それでは最後に、Hadoopとの連携を紹介します。

その他集計処理ミドル(Hadoop)との連携

Hadoopと組み合わせて使う

Hadoopは、大量のデータを複数のマシンで分散処理可能なプラットフォームです。MongoDBの開発元である10genは、HadoopとのコネクターとしてMongoDB Hadoop Adapter(mongo-hadoop)をgithubで公開しています。

公式マニュアルも充実しており、コマンドレベルでのGetting Started with Hadoopや、バッチシステム・データウェアハウス・ETLシステムの3つが紹介されたHadoop and MongoDB Use Casesがあります。

MongoDBとHadoopを組み合わせて使用することによって、データストアにMongoDBを使いながら、Hadoopの強力な分散機能により並列処理を行うことが可能となります図2参照⁠⁠。レプリカセット、シャーディング、Mongo Shellによる柔軟なクエリというデータストアとしてのMongoDBのメリットを活かしながら、Hadoopの分散処理機能を使えるという、お互いを補完し合う組み合わせが実現できます。

図2 MongoDBとHadoopの組み合わせ
図2 MongoDBとHadoopの組み合わせ

今回の記事では、MongoDB Hadoop Adapterについての紹介のみといたします。実際の使い方は公式マニュアルを参照ください。

まとめと次回のテーマ

今回はMongoDBで集計を実施する方法を紹介しました。MongoDBには、クエリライクに集計を実施できるAggregationフレームワークと、自分で処理内容を記述できるMap/Reduce機能とがあります。用途に応じて使い分けることが可能です。

また、近年需要が増加しているビッグデータへの集計処理には、Hadoopと組み合わせて使用するという方式があります。MongoDB開発元の10genがHadoop用のコネクタを提供しています。

次回はMongoDBの運用について紹介する予定です。お楽しみに!

おすすめ記事

記事・ニュース一覧