MongoDBにはMapReduceの仕組みが用意されてて楽しそうですよね。
MapReduce - Docs-Japanese - 10gen Confluence
ちゃんと日本語。
で、コンソールで入力しながらの開発は面倒なので、こんな時はMongo VUE。安くならないかなー。
Yet another MongoDB Map Reduce tutorial | MongoVUE
使い方、これじゃよく分からない。
How to perform MapReduce operations in MongoVUE | MongoVUE
こっちですね!この手順どおりに実行すれば、ちゃんとMapReduce!Sharding環境ならそれぞれのShardで分散処理してくれます。
せっかくなので、以前作ったMongoTraceListnerのサンプルを利用して、各URLの処理時間の平均をMapReduceで計算してみます。
public class PerformanceTraceModule : IHttpModule
{
private string ItemKey = "_mongoDbTraceStart";
public PerformanceTraceModule()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
var httpContext = (sender as HttpApplication).Context;
var startTime = httpContext.Items[ItemKey] = DateTime.Now;
}
void context_EndRequest(object sender, EventArgs e)
{
var httpContext = (sender as HttpApplication).Context;
var startTime = (DateTime)httpContext.Items[ItemKey];
Trace.WriteLine(new
{
Method = httpContext.Request.HttpMethod,
Status = httpContext.Response.StatusCode,
RawUrl = httpContext.Request.Url.ToString(),
Milliseconds = (DateTime.Now - startTime).Milliseconds
});
}
public void Dispose()
{
}
}
このHttpModuleを仕掛けて処理時間をMongoDBに入れます。
実行!何度も何度もリロードやら、About、Loginへのアクセスを繰り返す。
データも溜まって来ました。
ここで、MapReduceを実行。
Map
Reduce
Finalize
In & Out
結果
ただ、ここでmap/reduce/finalizeのコードに間違いがあっても、わかりにくい。どこのなにがエラーなのかサッパリ。そんな時はコンソールを見てみましょう。サービス化してたらログファイル。
finalizeで適当にエラーコードを入れて実行するとコンソールにエラーメッセージが出てます。開発中はMongoDB Shellでやるか、ローカル実行してコンソール確認しながらすすめるのがいいかもー。
Mongo VUEやShellで見るだけじゃなくて、ASP.NET MVCのアプリケーションから見れるようにするために、以下のようにしてみました。
private MongoDatabase GetDatabase()
{
var serverName = ConfigurationManager.AppSettings["MongoDb:Server"];
var databaseName = ConfigurationManager.AppSettings["MongoDb:Database"];
if (string.IsNullOrEmpty(serverName))
serverName = "mongodb://localhost";
if (string.IsNullOrEmpty(databaseName))
databaseName = "TraceListner";
var server = MongoServer.Create(serverName);
return server.GetDatabase(databaseName);
}
private BsonJavaScript GetMapReduceCode(string name)
{
return new BsonJavaScript(System.IO.File.ReadAllText(Server.MapPath("~/MapReduce/" + name)));
}
public ActionResult MapReduce()
{
var map = GetMapReduceCode("map.js");
var reduce = GetMapReduceCode("reduce.js");
var finalize = GetMapReduceCode("finalize.js");
var collection = GetDatabase().GetCollection("MyTrace");
var options = new MapReduceOptionsBuilder();
options.SetFinalize(finalize);
options.SetSortOrder("_id");
options.SetOutput(MapReduceOutput.Inline);
var result = from r in collection.MapReduce(map, reduce, options).GetResults()
select new
{
RawUrl = (string)r["_id"],
Count = r["value"].AsBsonDocument["Count"].ToInt32(),
Elapsed = r["value"].AsBsonDocument["Elapsed"].ToInt32(),
Average = r["value"].AsBsonDocument["Average"].ToDouble()
};
return Json(result.ToArray().OrderByDescending(mr=>mr.Average), JsonRequestBehavior.AllowGet);
}
HomeControllerに追加。
やったー。出たー。あとは、見た目よろしくしていけばOKでしょう。今回は気にしないけど。
こんな感じでMongoDBにデータ入れとけばMapReduceで集計をMongoDBサーバーに任せられていいですね!
っていう、結論じゃなくて、結局はV8でスクリプト動かしてるから、大量のデータの場合そんなに早くなかったりしてショック。対象データをすべてフェッチしてC#で処理したほうが早かったりも...。
速度的には思ったほどじゃないけど、これはこれで面白いので、PowerPivotとあわせて業務系の人が扱えるようになると、嬉しいですよねー。