MongoDBの集計機能が便利過ぎて泣けてくるお話し

- 作者: Kyle Banker,Sky株式会社玉川竜司
- 出版社/メーカー: オライリージャパン
- 発売日: 2012/12/14
- メディア: 大型本
- 購入: 5人 クリック: 55回
- この商品を含むブログ (4件) を見る
MongoDB集計機能
CentOSでNginxのログをFluentdを使ってMongodbにリアルタイムで格納する - Yuta.Kikuchiの日記
時給3000円のCEOと揶揄されている@yutakikucです。今日は簡単にMongodbのログ集計機能を紹介します。機能が豊富過ぎて泣けてくるんで、ログ解析する人は是非使ってみて下さい。FluentdでMongodbにNginxのLogを流し込む設定は上のエントリーを参照して下さい。次回はAggregationFramework/MapReduce周りについて触れたいと思います。
泣ける話 : 集計Query
インデックス - Docs-Japanese - 10gen Confluence
クエリー - Docs-Japanese - 10gen Confluence
MongodbはKVSでありながら、RDBの機能も持っているというところが便利で泣けます。具体例としてはIndex機能と、集計Queryをサポートしているところでしょうか。ここでは集計Queryについて説明します。集計QueryはRDBのlimit、count、group、sortなどが使えることや、比較演算子による条件指定もできます。また集計のデータ検索に正規表現が使えるところも魅力的です。それらを紹介していきます。条件指定はfind()、行数取得はcount()
RDBで言うSELECTとしてMongdbではfindを使います。MongdbのQueryでの条件指定はJSON形式だと思って下さい。x = 10 AND B = 20をJSONの条件とすると{ x : 10, y : 20 } こんな感じです。またfindでは正規表現の指定ができるので、特定のRequestURIを含むログだけを出力したいときなどに便利です。例えば/api/userというURIへのaccessは{ path : /\/api\/user/ }のように指定します。また検索されたレコードの個数をカウントする場合はcountを利用します。
//登録されているデータベースを表示 > show dbs local (empty) nginx 0.203125GB // dbを使用 > use nginx switched to db nginx // collectionを表示 > show collections nginx_access system.indexes // findによりnginxのアクセスを出力 > db.nginx_access.find() { "_id" : ObjectId("5123476fe138231086000001"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/", "code" : "200", "size" : "612", "referer" : "-", "agent" : "w3m/0.5.2", "time" : ISODate("2013-02-19T09:35:35Z") } { "_id" : ObjectId("51234790e138231086000002"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/", "code" : "200", "size" : "612", "referer" : "-", "agent" : "w3m/0.5.2", "time" : ISODate("2013-02-19T09:36:14Z") } { "_id" : ObjectId("5177c10ee138231b1b000001"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/api/user/test", "code" : "404", "size" : "570", "referer" : "-", "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "time" : ISODate("2013-04-24T11:24:53Z") } // 条件にstatus codeが200OKだけを指定 > db.nginx_access.find({code:'200'}) { "_id" : ObjectId("5123476fe138231086000001"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/", "code" : "200", "size" : "612", "referer" : "-", "agent" : "w3m/0.5.2", "time" : ISODate("2013-02-19T09:35:35Z") } { "_id" : ObjectId("51234790e138231086000002"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/", "code" : "200", "size" : "612", "referer" : "-", "agent" : "w3m/0.5.2", "time" : ISODate("2013-02-19T09:36:14Z") } // 条件にstatus codeが200OKのアクセスをカウント > db.nginx_access.find({code:'200'}).count() 2 // /api/userというRequestURIを含むアクセスを出力 > db.nginx_access.find({path:/\/api\/user/}) { "_id" : ObjectId("5177c10ee138231b1b000001"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/api/user/test", "code" : "404", "size" : "570", "referer" : "-", "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "time" : ISODate("2013-04-24T11:24:53Z") } { "_id" : ObjectId("5177c145e138231b1b000005"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/api/user", "code" : "404", "size" : "570", "referer" : "-", "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "time" : ISODate("2013-04-24T11:25:57Z") }条件演算子の利用
Mongodbの数値の大小などを比較する演算子の指定に$を利用するのでやや可読性に欠けますが、覚えてしまうと簡単に記述することが出来ます。以下に比較演算子の表を記載します。ついでにsortの条件も表にまとめます。下の条件演算子を利用して特定の日付のデータを降順にして最大3件表示するようなMongo Shellを書いてみます。条件演算としては$gtと$ltとandを使用し、sort/limit関数でデータの並び替えと制限を付けます。
一般的な条件演算子 Mongodb find条件演算子 x == y find( { x : y } ); x > y find( { x : { $gt : y } } ); x < y find( { x : { $lt : y } } ); x >= y find( { x : { $gte : y } } ); x <= y find( { x : { $lte : y } } ); x != y find( { x : { $ne : y } } ); x == y and y == z find( { x : y , y : z } ); x == y or y == z find( { $or : [ { x : y }, { y : z } ] } ); x == y not or y == z find( { $nor : [ { x : y }, { y : z } ] } ); exists( x ) find( { x : { $exists : true } } ); not exists( x ) find( { x : { $exists : false } } ); x % y == z find( { x : { $mod : [ y, z ] } } ); w in ( x, y, z ) find( { w : { $in : [ x, y, z ] } } ); w not in ( x, y, z ) find( { w : { $nin : [ x, y, z ] } } );
sort条件 Mongodb sort条件 order by name asc sort( { name : 1 } ); order by name desc sort( { name : -1 } ); > db.nginx_access.find({time:{"$gt" : ISODate("2013-04-24T00:00:00+09:00"), "$lt" : ISODate("2013-04-25T00:00:00+09:00")}}).sort( {time:-1}).limit(3); { "_id" : ObjectId("5177c187e138231b1b00000f"), "host" : "127.0.0.1", "user" : "-", "method" : "-", "code" : "400", "size" : "0", "referer" : "-", "agent" : "-", "time" : ISODate("2013-04-24T11:26:56Z") } { "_id" : ObjectId("5177c17ce138231b1b00000d"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/", "code" : "304", "size" : "0", "referer" : "-", "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "time" : ISODate("2013-04-24T11:26:46Z") } { "_id" : ObjectId("5177c17ce138231b1b00000e"), "host" : "127.0.0.1", "user" : "-", "method" : "GET", "path" : "/favicon.ico", "code" : "404", "size" : "570", "referer" : "-", "agent" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4", "time" : ISODate("2013-04-24T11:26:46Z") }
泣ける話 : Javascript Shell
mongo Shell JavaScript Quick Reference ― MongoDB Manual 2.4.3
Mongodbを操作する外部Javascriptファイルを記述して、コマンドラインから実行できます。PHPやRubyからも実行出来ますが、これらの言語のDriverを必要とせずJavascriptだけでできます。Javascriptからdbを読み込む場合はnew Mongo().getDB( "dbname" ); collectionを呼び出す場合はdb.getCollection( "collectionname" );を利用します。取得したデータを繰り返し操作するforEachも使えますし、print関数を使用すれば標準出力してくれます。例えば以下のようなScriptファイルをmongo_shell.jsと保存しコマンドラインからmongo nginx < mongo_shell.jsを実行します。また外部Javascriptファイルより機能は制限されますが、mongoコマンドの--evalオプションを使うと外部ScriptにしなくてもそのままMongodbを操作することが出来ます。--quietでオプションで結果のみを出力します。私が試したところ--evalではfind()の結果が取れず、count()を付けると実行できました。var db = new Mongo().getDB( "nginx" ); var col = db.getCollection( "nginx_access" ); var data = col.find().sort( { agent:1 } ); print( "User Agent Summary \n" ); data.forEach( function( data ) { print( "Agent = " + data.agent ); } );$ mongo nginx < mongo_shell.js MongoDB shell version: 2.2.3 connecting to: nginx User Agent Summary Agent = - Agent = - Agent = - Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 Agent = w3m/0.5.2 Agent = w3m/0.5.2$ mongo nginx --quiet --eval 'db.nginx_access.find().count();' 17
泣ける話 : rmongodb
rmongodb
CentOSでR言語を使ってみたことのまとめ - Yuta.Kikuchiの日記
分析系の言語であるRからmongodbに接続することが出来ます。R言語の様々な分析関数を使うと上手いように集計ができると思います。R言語の設定は以下に簡単に書きますが、tcl/tkのバージョン問題を解決するためにrpmのforceでinstallしているのであまりお薦めはしません。R起動後に install.packages("rmongodb")をにてpackageをinstallします。以下のR言語Scriptをmongdb.Rというファイル名で保存してR起動後にsourceにて呼び出します。そうするとnginx_accessログ総数のcountとfindの結果の一覧が出力されます。またR言語Scriptをmongdb.Rという外部ファイル名で保存してsourceにて呼び出します。そうするとnginx_accessログ総数のcountとfindの結果の一覧が出力されます。R言語で取取得したデータを基にbarplot等を使ってMongodbのデータを可視化することができます。以下ではstatus codeを見てそのRequest数をbarplotしています。$ wget http://ftp.riken.jp/Linux/caos/centos/5/os/x86_64/CentOS/tcl-8.4.13-6.el5.x86_64.rpm $ wget http://ftp.riken.jp/Linux/caos/centos/5/os/x86_64/CentOS/tk-8.4.13-5.el5_1.1.x86_64.rpm $ sudo rpm -ivh --force tcl-8.4.13-6.el5.x86_64.rpm tk-8.4.13-5.el5_1.1.x86_64.rpm $ sudo yum install R R-devel -y $ R > install.packages("rmongodb")library(rmongodb) #connect mongo <- mongo.create() if( mongo.is.connected(mongo) ) { #count cnt <- mongo.count(mongo, 'nginx.nginx_access') print( cnt ) #find objs <- mongo.find(mongo, 'nginx.nginx_access') while( mongo.cursor.next(objs) ) { print( mongo.cursor.value(objs) ) } }> source( "/home/yuta/mongo.R" ) [1] 17 _id : 7 5123476fe138231086000001 host : 2 127.0.0.1 user : 2 - method : 2 GET path : 2 / code : 2 200 size : 2 612 referer : 2 - agent : 2 w3m/0.5.2 time : 9 1361266535000library(rmongodb) #connect mongo <- mongo.create() if( mongo.is.connected(mongo) ) { query <- list( code="200" ) r200 <- mongo.count( mongo, "nginx.nginx_access", query ) query <- list( code="304" ) r304 <- mongo.count( mongo, "nginx.nginx_access", query ) query <- list( code="400" ) r400 <- mongo.count( mongo, "nginx.nginx_access", query ) query <- list( code="404" ) r404 <- mongo.count( mongo, "nginx.nginx_access", query ) y <- c( r200, r304, r400, r404 ) barplot( y, ylab='request count', xlab='status', names.arg=c("200", "304", "400", "404"), col=c("gray", "yellow", "orange", "orangered") ) }