MongoDBでゆるふわDB体験

第10回 MongoDBでの集計処理

この記事を読むのに必要な時間:およそ 5 分

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で集計してみましょう。

著者プロフィール

藤崎祥見(ふじさきしょうけん)

野村総合研究所 OpenStandia所属。オープンソースのR&Dとセミナー講師を担当。

Debian,Ubuntu,Liferayのコミュニティで活動した後,MongoDBの翻訳に関わり,丸の内MongoDB勉強会を始める。

実家がお寺で,住職の資格を所持する坊主系エンジニア。

Twitter:@syokenz


渡部徹太郎(わたなべてつたろう)

野村総合研究所 OpenStandia所属。オープンソースを使ったSIやサポートの業務に従事。

藤崎と共同で丸の内MongoDB勉強会を始める。

趣味は自宅サーバ。好きなものはLinuxとRuby。

Twitter:@fetarodc


林田敦(はやしだあつし)

野村総合研究所 OpenStandia所属。オープンソースを使ったSIや製品開発業務に従事。

丸の内MongoDB勉強会では広報兼雑用係を務める。

趣味はレザークラフト,ダイビング,スキー,キャンプ,ジェットスキー,カメラ等々。作って滑って撮って潜れるエンジニア。

Facebook:Atsushi Hayashida

コメント

コメントの記入