MongoDBでビッグデータと遊んでみた


## なんだそれ
### MongoDBについて
https://ja.wikipedia.org/wiki/MongoDB
>・MongoDBはRDBMSではなく、いわゆるNoSQLと呼ばれるデータベースに分類されるものである。

ビッグデータとか扱うやつですね。

>・「ドキュメント」と呼ばれる構造的データをJSONライクな形式で表現し、そのドキュメントの集合を「コレクション」として管理する

要するにユニークハッシュ値とjsonの組み合わせの連想配列。

>・RDBMSのように高度な結合操作を効率的に行うことはできないが、データの追加・更新・削除・クエリは高速に行うことができる。

すごい。

>・Mongoという名前は、英語で「ばかでかい」を意味する "humongous" に由来する。

笑った。

### ビッグデータについて
https://ja.wikipedia.org/wiki/ビッグデータ

>市販されているデータベース管理ツールや従来のデータ処理アプリケーションで処理することが困難なほど巨大で複雑なデータ集合の集積物を表す用語である。

要するに超沢山データがある。


## 早速インストール
```Terminal
brew install mongodb
```
>To have launchd start mongodb at login:
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
Then to load mongodb now:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist

本体のインストール。
スタート時の設定もしようね、との事。

```Terminal
ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
```
言われた通りに、おまじないの実行。

```Terminal
mongo -version
```
>MongoDB shell version: 3.2.0

インストール完了。やったぜ。


## 適当にデータを流してみる
調べてみると、どうやらmongoはjavaScriptでグリグリ出来るらしい。
適当な所にjsファイル作成、まずは意味ない適当なデータを突っ込んでみる。

```Terminal
cd
mkdir mongo
vi create.js
```
こうして。

```javaScript
for (var i = 0; i < 100; ++i) {
  db.test.save({
    hoge : "hoge" + i
  });
}
```
適当にinsert分に当たるものを書いて

```Terminal
mongo --quiet localhost/test create.js
```
コレでmongoDBにズドン。　100件程度なので一瞬。

## mongoDB上で見てみる
```Terminal
mongo
```
mongoにアクセス。

```mongoDB
db.test.find()
```
>{ "_id" : ObjectId("56a3111fcaf1a738f31cfce7"), "hoge" : "hoge0" }
{ "_id" : ObjectId("56a3111fcaf1a738f31cfce8"), "hoge" : "hoge1" }
{ "_id" : ObjectId("56a3111fcaf1a738f31cfce9"), "hoge" : "hoge2" }
...

おぉ！！入っている！
でもこういうデータだと、弄りようが無いので適当に妄想して遊ぶ事に。

```mongoDB
db.test.drop()
```
今回作ったのは、もう使わないので消しちゃいましょう。

## 適当なシュミレーションとデータ設計
### シュミレーション(妄想)
とあるホゲホゲ企業の購入履歴データを、過去5年間分、集計＆調査する事になった可哀想な僕。
なんでも、ユーザー数は **1,000人程** だけど、ログ件数は **10,000,000件** ぐらいあるらしい。
~~妄想の世界の住人は、色々おかしい。~~

#### 妄想の元に書いた、ログデータ作成スクリプト

```create.js
const LOGS = 10000000;
const MAX_USER_ID = 1000;
const UNIXTIME_YEAR = 31536000;
const MAX_PURCHASE_PRICE = 10000;

function getRandomNumber(max) {
    return Math.floor(Math.random() * (max + 1));
}

var date = new Date();
function getRandomUnixTime(offset) {
    return Math.floor(date.getTime() / 1000) - getRandomNumber(offset);
}

for (var i = 0; i < LOGS; ++i) {
	db.purchaseLog.save({
		user_id        : getRandomNumber(MAX_USER_ID),
		purchase_price : getRandomNumber(MAX_PURCHASE_PRICE),
		purchase_at    : getRandomUnixTime(UNIXTIME_YEAR * 5),
	});	
}
```
コードの説明は不要かと思います。
上に書いた妄想通りです。

```Terminal
mongo --quiet localhost/test create.js
```
先ほどは100件程度のデータだったので一瞬で突っ込めましたが
今回は **10,000,000件** なので、流石にRDBMSより早いとは言っても、それなりの時間がかかります。
早く終る事を祈りつつズドン。

## 弄る準備をする。
**10,000,000** 件のデータが無事、クラッシュする事無くinsertされました。
ちなみに、私の ~~ショボイ~~ Mac pro(4G)だと **1時間7分23秒** かかりました。

早速作ったデータを見ていきます。

```mongoDB
db.test.find()
```
>local  0.000GB
test   0.404GB

10,000,000件入れても、jsonのサイズ自体は小さいので400MB程度でした。


```mongoDB
db.test.find()
```
>{ "_id" : ObjectId("56a31a2d201cdb2f439ca4ae"), "user_id" : 655, "purchase_price" : 9357, "purchase_at" : 1438883196 }
{ "_id" : ObjectId("56a31a2e201cdb2f439ca4af"), "user_id" : 816, "purchase_price" : 6915, "purchase_at" : 1380445364 }
{ "_id" : ObjectId("56a31a2e201cdb2f439ca4b0"), "user_id" : 851, "purchase_price" : 4786, "purchase_at" : 1382838307 }
...

一応データも確認、想定通りです。
このまま弄るのも良いのですが、ちょっと色々と面倒なのでラッパーを書きます。

#### 弄る為のjavaScriptを適当に用意。
```find.js
var results = db.purchaseLog.find({user_id:100});

//  suck
function formatDate(unixtime) {
    var date = new Date(unixtime * 1000);
    date.setTime(date.getTime() + (60 * 60 * 1000));
    var zeroPadding = function(num) {
        return ("0" + num).slice(-2);
    }
    var year = date.getFullYear();
    var month = zeroPadding(date.getMonth() + 1);
    var day = zeroPadding(date.getDate());
    var hour = zeroPadding(date.getHours());
    var min = zeroPadding(date.getMinutes());
    var sec = zeroPadding(date.getSeconds());
    return year + "-" + month + "-" + day + " " + hour + ":" + min + ":" + sec;
}

var counter = 0;
results.forEach(function(result) {
	++counter;
	print("id: " + result.user_id + "\t\t" + "price: " + result.purchase_price + "\t" + "date: " + formatDate(result.purchase_at));
});
print(counter);
```
大事な所は、一番上の行だけです。
**db.purchaseLog.find(...)** の所が、SQLのクエリ部分です。
サンプルで、単にuser_idが100番の人を抽出してみました。
後は表示に必要な処理を書いただけです。

注意点としては、データの取得条件は、特殊な場合を除いてfind()部分で取得するようにして下さい。
いくら早くてもforEach部分で処理してしまうと、パフォーマンスが落ちます。

~~**後、javaScriptの日付操作って超面倒なんですね**~~


## 実際に動かす
```Terminal
mongo --quiet localhost/test find.js
```
>id: 100         price: 9617     date: 2012-08-20 16:42:24
id: 100         price: 5410     date: 2014-12-11 17:48:45
id: 100         price: 1506     date: 2011-05-30 11:18:03
...

**３秒ぐらいで結果が出ました、早い、早すぎる。**
RDBMS上で処理すると、１分以上はかかる処理ではないでしょうか。
色々条件を変えたりして弄ってみると分かりますが、 **どれもメチャ早いです。**


## まとめ
### 予想以上に早い
10,000,000件のデータを弄っているとは思えない程早くて驚きました。
この例だと、javaScriptを流して10秒以上待たされる事は無かったです。

RDBMSはマスターデータおじさんで、noSQLはトランデータお兄さんな印象を受けました。

一般的にビックデータと言われる、膨大なデータを扱う事に興味を持った方は
一度弄ってみてはどうでしょうか。

**世界が変わります。**


### ちなみに
```totalPrice.js
db.purchaseLog.aggregate([
	{ $group: { _id: "$user_id", total: { $sum: "$purchase_price" }}},
	{ $sort:  { total: 1}}
]).forEach(function(result) {
	print("id: " + result._id + "\t\t" + "totalPrice: " + result.total);
});
```
>...
id: 795         price: 51749038

過去５年通して、一番使ってた人。
複雑な条件になると、ちょっとネストが見づらい。　ymlで書きたい。

このコードはidでグループ化して合計金額を全員分集計し、ソートしていますが
**実行速度は、14秒でした。**
ﾊﾟﾈｪ!!

おわり。