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とあわせて業務系の人が扱えるようになると、嬉しいですよねー。