主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

MongoDB: ブログのデータストアに使おうとしてみる

                
tags: mongoDB NoSQL
 リレーショナルデータベースよりできることを絞り、パフォーマンスの向上を図ったNoSQL。そのデータの保存形式などはCassandraなりMongoDBなり、Gihyoとかcodezineに詳しい解説記事がある。NoSQLデータベースを使う上でもう一つ知りたいのは、自分の作りたいもののデータストアにできる程度には柔軟にデータを引き出せるかということだ。たとえばブログを作ろうとしてみると、そのデータストアにCassandraを使うのは簡単ではなかった。
Cassandra: ブログのデータストアに適用しようとしてみる

 ではMongoDBはどうだろう。とりあえずの触りとしてMySQLやSQLiteに代替できるかを知りたいので、用語はMongoDBでの正式なものでなく、リレーショナルデータベースで似た概念のものを使う。行とか列とかテーブルとか。
 Cassandraのときと同様にブログを作ろうとしてみる。ブログ記事のデータとしてほしいのは、タイトル、本文、著者、投稿時間、タグあたりだ。記事一件につきこれらを一つの行にまとめて、それをさらにテーブルにまとめて格納する。MongoDBではテーブルに格納するデータの構造をあらかじめ決めておく必要はないので、データの適切な保存場所を決めたらそのままデータをそこに入れる。今回はMongoDBのPythonドライバであるpymongoを使う。

 MongoDBサーバを起動したらpymongoで接続して準備から。
import datetime

import pymongo


host = "xxx.xxx.xxx.xxx"
client = pymongo.MongoClient(host, 27017)

db = client.test_database


 続いてブログの記事データを7件、書き込んでみる。
db.blog.insert({"title":"foo1",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 3),
"tags":["Python", "MongoDB"]
})

db.blog.insert({"title":"foo2",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 6),
"tags":["Python"]
})

db.blog.insert({"title":"foo3",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 11),
"tags":["MongoDB"]
})

db.blog.insert({"title":"foo4",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 13),
"tags":["Ruby"]
})

db.blog.insert({"title":"foo5",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 16),
"tags":["Ruby", "Rails"]
})

db.blog.insert({"title":"foo6",
"author":"john",
"body":"",
"date":datetime.datetime(2011, 1, 19),
"tags":["C#", "MongoDB"]
})

db.blog.insert({"title":"fooooo!",
"author":"alex",
"body":"",
"date":datetime.datetime(2011, 1, 19),
"tags":["fooooo!"]
})


 データを保存したので、今度は読みだしてみる。ブログ記事は普通、著者名でフィルタが掛けられ、日付が新しい順に並ぶのでそのとおりに。
rows = db.blog.find({"author":"john"}).sort("date",pymongo.DESCENDING)
for row in rows:
print row

出力は以下のとおり。
>>>
{u'body': u'', u'author': u'john', u'title': u'foo6', u'tags': [u'C#', u'MongoDB'], u'date': datetime.datetime(2011, 1, 19, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94f')}
{u'body': u'', u'author': u'john', u'title': u'foo5', u'tags': [u'Ruby', u'Rails'], u'date': datetime.datetime(2011, 1, 16, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94e')}
{u'body': u'', u'author': u'john', u'title': u'foo4', u'tags': [u'Ruby'], u'date': datetime.datetime(2011, 1, 13, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94d')}
{u'body': u'', u'author': u'john', u'title': u'foo3', u'tags': [u'MongoDB'], u'date': datetime.datetime(2011, 1, 11, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94c')}
{u'body': u'', u'author': u'john', u'title': u'foo2', u'tags': [u'Python'], u'date': datetime.datetime(2011, 1, 6, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94b')}
{u'body': u'', u'author': u'john', u'title': u'foo1', u'tags': [u'Python', u'MongoDB'], u'date': datetime.datetime(2011, 1, 3, 0, 0), u'_id': ObjectId('532a46a357efc978a91ff94a')}

authorがjohn、dateが新しい順に並んでいるので期待通り。ではCassandraで難しかったLIMITとOFFSETはどうだろう。MongoDBはこれをそなえている。findメソッドに引数としてlimitを指定して数値を渡せばLIMITと同様に働き、skipを指定して数値を渡せばOFFSETと同様に働いてくれる。
rows = db.blog.find({"author":"john"}, limit=3, skip=3).sort("date",pymongo.DESCENDING)
for row in rows:
print row


 さらにtagsで記事にフィルタをかけたい。これも楽にできる。
rows = db.blog.find({"author":"john", "tags":"Python"}, limit=3, skip=0).sort("date",pymongo.DESCENDING)
for row in rows:
print row

これができるのがMongoDBのうれしいところ。タグを実装して多対多をやりたいとすると、従来のリレーショナルデータベースでは中間テーブルを設けなければならなかった。MongoDBでは列に配列を使って複数の文字列や数値を入れられるので中間テーブルいらず。

 どうやらブログ記事のデータストアとしてMongoDBは適していそうだ。それがわかったので、最後にクエリのパフォーマンスアップの方法としてインデクス作成をやっておく。
 インデクスを作っていない状況では、テーブルに入っているすべての行に対して、指定した条件を通過するかのチェックが行われる。今だとauthorが"john"である行を呼ぶのに、7件の行にスキャンをかけている。その確認は以下のように。
IN: db.blog.find({"author":"john"}, limit=0, skip=0).sort("date",pymongo.DESCENDING).explain()["nscanned"]
OUT: 7

 ここでインデクスを作ってみる。authorが"john"という条件でついでに日付の若い順という並べ替えもしておく。
IN: db.blog.create_index([("author", pymongo.ASCENDING), ("date", pymongo.DESCENDING)])
OUT: u'author_1_date_-1'

 もう一度スキャン件数を確認してみる。
IN: db.blog.find({"author":"john"}, limit=0, skip=0).sort("date",pymongo.DESCENDING).explain()["nscanned"]
OUT: 6
スキャン件数が6件に減った。保存されている行が多くなるほどこのインデクスの恩恵は増えていく。


 すでに著名企業への導入もされているNoSQLデータベースのMongoDB。これが個人でWebアプリに使うのに適しているかを確認したくてブログのデータストアに使うことをシミュレートしてみた。保存されたデータを絞り込み、並べ替えをおこなって引き出すことも容易にでき、パフォーマンス向上のためにインデクスも作れる。これだったら簡単に使えそうだ。
            

コメントの投稿

非公開コメント

プロフィール

Matoba

Author:Matoba

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。