Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

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

MongoDBイン・アクション

MongoDBイン・アクション

MongoDB集計機能

CentOSでNginxのログをFluentdを使ってMongodbにリアルタイムで格納する - Yuta.Kikuchiの日記 はてなブックマーク - 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
クエリー - 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$ltandを使用し、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 はてなブックマーク - mongo Shell JavaScript Quick Reference ― MongoDB Manual 2.4.3
Mongodbを操作する外部Javascriptファイルを記述して、コマンドラインから実行できます。PHPRubyからも実行出来ますが、これらの言語の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の日記 はてなブックマーク - 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 	 1361266535000
library(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") )
}