LoginSignup
39

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-01-23

なんだそれ

MongoDBについて

・MongoDBはRDBMSではなく、いわゆるNoSQLと呼ばれるデータベースに分類されるものである。

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

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

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

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

すごい。

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

笑った。

ビッグデータについて

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

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

早速インストール

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

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

ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mongodb.plist

言われた通りに、おまじないの実行。

mongo -version

MongoDB shell version: 3.2.0

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

適当にデータを流してみる

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

cd
mkdir mongo
vi create.js

こうして。

for (var i = 0; i < 100; ++i) {
  db.test.save({
    hoge : "hoge" + i
  });
}

適当にinsert分に当たるものを書いて

mongo --quiet localhost/test create.js

コレでmongoDBにズドン。 100件程度なので一瞬。

mongoDB上で見てみる

mongo

mongoにアクセス。

db.test.find()

{ "_id" : ObjectId("56a3111fcaf1a738f31cfce7"), "hoge" : "hoge0" }
{ "_id" : ObjectId("56a3111fcaf1a738f31cfce8"), "hoge" : "hoge1" }
{ "_id" : ObjectId("56a3111fcaf1a738f31cfce9"), "hoge" : "hoge2" }
...

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

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),
    }); 
}

コードの説明は不要かと思います。
上に書いた妄想通りです。

mongo --quiet localhost/test create.js

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

弄る準備をする。

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

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

db.test.find()

local 0.000GB
test 0.404GB

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

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の日付操作って超面倒なんですね

実際に動かす

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
...

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

まとめ

予想以上に早い

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

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

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

おわり。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39