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

MongoDBでオートインクリメントを・・・という話をほんの少し先まで

                
tags: C# mongoDB
MongoDBでドキュメントの任意のフィールドをオートインクリメントにしたい・・・というサンプルは本家にある。
https://docs.mongodb.com/v3.0/tutorial/create-an-auto-incrementing-field/

関数を使うわけだが、その時点で開いてるターミナルを閉じたらその関数は消える。なので永続化として下記のように保存、保存したものを呼び出して使える。
db.system.js.save({_id:'nextSeq',
value:function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
} );
return ret.seq;
}
});

db.system.js.findOne({_id:"nextSeq"}).value("userid");


C#ではストアしたJavaScriptはBsonJavaScriptとして取得できる。だけどどう実行するの・・・ドキュメントでもサンプルでも使用例が見つからない。なのでストアされたものを使わずに自分で書く。
var fooCollection = db.GetCollection("foo");
var d = new BsonDocument
{
{ "a", new BsonJavaScript("(function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
});
return ret.seq;
})('entry_id')")
}
};
fooCollection.InsertOne(d);
スポンサーサイト



Python: Tornadoのノンブロッキングという特徴を手っ取り早く試す

                
 PythonでのWebアプリ作りはGoogleAppEngineから入った。GoogleAppEngineで使うフレームワークが最初はwebappでそれがwebapp2になったが、それが一般的なフレームワークではないので、PythonのWebフレームワークで他のサーバでも使えて開発が続いているのを選ばねばとなった。それでwebapp系に割と似た書き方をするし面白い特徴を持っているTornadoを選んだ。
 Tornadoはノンブロッキングなサーバというのをウリとしている。ノンブロッキングという特徴を利用すれば、時間のかかるIO処理があったらそのデータ処理開始はIOが終わるまで後回しにして、次のリクエストの処理を先にやってしまえる。このあたりを試していなかったので今回試してみる。ちなみにノンブロッキングではなくPython標準のWSGIでも使える。

 リクエスト処理中に起こった時間のかかるIO処理は後回しにして、次のリクエストの処理を始める。これを検証する。
 時間のかかるIO処理として、MongoDBから10MBのデータを取ってくる処理を行うことにする。そのためにMongoDBを立ち上げて、サイズが10MBほどの適当なドキュメントを一件作成しておく。PyMongoを使うとおおよそ下記のように。
v = "a" * 10 ** 7
db.tekitou.insert({"x":v})


 以降のPythonスクリプトではPyMongoではなくTornado用に書かれたMotorを使う。

 上記のドキュメントの取得を、Tornadoの任意のリクエストハンドラの処理にはさむ。
class AsyncHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
data = yield db.tekitou.find_one()
self.write(data["x"])


 軽い処理ですんなり終えられそうな処理をするリクエストハンドラも用意しておく。
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")


 二つのリクエストハンドラを書いた。これらをパスへ割り当てる。今回は軽い処理しかないリクエストハンドラをルート"/"、MongoDBから10MB取ってくる処理を持つリクエストハンドラをパス"/b"へ割り当てておく。
 もしパス"/b"へのリクエストが先にあり、次にパス"/"へのリクエストがあったのに、パス"/"にリクエストしたクライアントが先にレスポンスを得られれば順序が変わっており、ノンブロッキングな特徴が確認できたことになる。

 Pythonのmultiprocessingモジュールを使って、用意したサーバに計四つのプロセスでリクエストを送り、リクエスト順に対するレスポンス順の変化があるかを調べる。リクエストに使ったスクリプトを下につけておく。まずパス"/b"へのリクエストを先に二件発生させ、次にパス"/"へのリクエストを二件発生させている。リクエストごとに固有の整数値を与えておいて、それを判別のためにリクエストクエリに入れておく。
from urllib import request
from multiprocessing import Process
import datetime
import time


url1 = "http://localhost:8888/"
url2 = "http://localhost:8888/b"


def req(url, request_id):
time.sleep(0.01 * request_id)
t = datetime.datetime.now()
print(str(request_id) + ":" + str(t))
f = request.urlopen(url + "?num=" + str(request_id))
print(str(request_id))


if __name__ == '__main__':
p1 = Process(target=req, args=(url2, 1))
p1.start()

p2 = Process(target=req, args=(url2, 2))
p2.start()

p3 = Process(target=req, args=(url1, 3))
p3.start()

p4 = Process(target=req, args=(url1, 4))
p4.start()


 サーバ側は最終的に下記。どのプロセスからのリクエストかをGETのクエリから拾っている。
import tornado.ioloop
import tornado.web
import motor


client = motor.MotorClient()
db = client.tornado_test


class MainHandler(tornado.web.RequestHandler):
def get(self):
print(self.get_argument("num"))
self.write("Hello, world")


class AsyncHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
print(self.get_argument("num"))
data = yield db.tekitou.find_one()
self.write(data["x"])


application = tornado.web.Application([
(r"/", MainHandler),
(r"/b", AsyncHandler),
])


if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()


 スクリプトの用意ができたので走らせる。MongoDBを立ち上げた状態でTornadoサーバを立ち上げ、リクエストスクリプトを実行する。コマンドプロンプトをキャプチャしたものを下に。上がサーバを走らせているプロンプト、下がリクエストを書いたスクリプトを走らせたプロンプトである。
1503291752257.jpg
 サーバを走らせている上側を見るとリクエストIDの1から順番にリクエストを受け取っているのがわかる。次に下を見るとリクエストも確かにその順番で発生していると思われる。しかしレスポンスの順番がかわっており、3、4、1、2の順になっている。
 3と4は重いIOの発生しないリクエストハンドラへのリクエストであった。3と4のリクエストが1と2のリクエストのあとに発生しているのに、レスポンスを受け取ったのは3と4のほうが早い。これはMongoDBへの10MBのIO処理を待っている間に重いIO処理を含まない3と4への処理を先に済ませたからだと考えられる。つまりTornadoのサーバであるノンブロッキングという特徴が見られたということだろう。

 数件のリクエストに対するレスポンスの順番変更があるかを探ることで、Tornadoサーバのノンブロッキングという特徴を試してみた。確かに重いIO処理を行っていると考えられる最中にも別のリクエストを処理し、レスポンスの順番が変わっていた。Tornadoの特徴であるノンブロッキングというところが確認できた。

MongoDB VS. MySQL(with PyMongo or MySQL Connector)の続き

                
前回:http://elicon.blog57.fc2.com/blog-entry-406.html

 前回MongoDBとMySQLを自分の与えたコンディションで比較してみた。データの挿入はMongoDBのほうがすぐれていて、データのクエリと引き出しはMySQLのほうがすぐれているという結果だった。ここで終わってはどっち使えばいいのという疑問に一歩踏み込めていなくて引っかかるので、ケーススタディとして前回に与えたコンディションではどっちを使うのがいいかをもう少し検討してみる。ただあくまでケーススタディとしてやっているだけなので、値は概算を使ったりする。

 書き込み一件当たりの消費時間と、一件当たりのクエリと引き出しの消費時間を出してみる。
 書き込みは前回で10000件連続書き込みをやっているのでそこから算出。
MongoDB: 3.58[s]→一件あたり0.358[ms]
MySQL: 18.6[s]→一件あたり1.86[ms]
 続いてクエリと引き出し。
MongoDBでは以下のように。
t = time.time()
i = book_collection.find_one({"author":"Albert Camus"}, sort=[("rate",pymongo.ASCENDING)], skip=1000)
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":"Dostoevsky"}, sort=[("publishedDate", pymongo.ASCENDING)], skip=1000)
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":"Herman Hesse", "rate":3}, sort=[("publishedDate", pymongo.ASCENDING)])
print("SELECT: " + str(time.time() - t))
assert(not i is None)

t = time.time()
i = book_collection.find_one({"author":{"$in":["Thomas Man", "William Golding"]}}, sort=[("publishedDate", pymongo.ASCENDING)])
print("SELECT: " + str(time.time() - t))
assert(not i is None)

 MySQLでは以下のように。
t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Albert Camus' ORDER BY rate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Dostoevsky' ORDER BY publishedDate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author = 'Herman Hesse' AND rate = 3 ORDER BY publishedDate LIMIT 1 OFFSET 0")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

t = time.time()
cursor.execute("SELECT * FROM books WHERE author IN ('Thomas Man', 'William Golding') ORDER BY publishedDate LIMIT 1 OFFSET 1000")
i = cursor.fetchone()
print(time.time() - t)
assert(not i is None)

 引き出しの結果は以下。
1411111652098.jpg
 平均から大きく外れそうなデータははぶく。その上で最大値と最低値の平均をデータとして使うことにする。MySQLのパフォーマンスに大きく優位性が出ている。ここらはキャッシュ未設定で行っていたので、もしかしたらどちらもまだパフォーマンスを伸ばせるかもしれない。

MongoDB: 17[ms]
MySQL: 0.45[ms]

 以上で検討に必要なデータはそろえられた。データの書き込みと読み込みの比と処理時間の関係をMongoDBとMySQLのそれぞれでグラフ化する。

 上記のグラフから、書き込み回数が読み込みの回数の10倍を超えるとMySQLの処理時間がMongoDBを上回って増えているのがわかる。一方で10倍以下ではMySQLのほうがパフォーマンスがよかった。ここらはキャッシュ未設定で行っていたので、もしかしたらどちらもパフォーマンスを伸ばせるかもしれない。



 今回はMongoDBとMySQLのパフォーマンス比較をしたかったので、それぞれのデータストアをデフォルト状態で走らせ、少し適当な条件設定をして比較を行った。その条件下では書き込みを読み込みの10倍以上の頻度で行うならMongoDBを、そうでないならMySQLを使うといいということがわかった。
 しかし実際の状況となればそれぞれのデータストアを設定なしにデフォルトで使うことはないと思うし、クエリからデータを引き出すのも一件だけでなく複数件引き出すことも多々あり、今回の状況と違う。データストアへの命令も書き込みと読み込みだけではなく、更新や削除を含め多々ある。そういうことを考えていくと、目的を最高の効率で果たしてくれるデータストアを選ぶのは複雑で難しいと痛感する。

MongoDB VS. MySQL(with PyMongo or MySQL Connector)

                
 MongoDBのパフォーマンスとやらがアピール通りに優れているのか気になったので、MongoDBとMySQLのパフォーマンス比較をそれぞれの公式Pythonドライバを通じてやってみた。条件としてはVMに入れたLinux Mintで、MongoDBもMySQLもインストール直後のデフォルト状態で走らせた。
VirtualBox, CPU 1core, Memory 2GB
Linux Mint MATE Debian(64bit)
MongoDB 2.6.5, PyMongo 2.7.2
MySQL 5.5.33-1, MySQL Connector 2.0.2
Python 3.4.2

 レコードを1件ずつ10000回の書き込みと、そこからクエリによる100件のレコード取得を4パターンで行う。Pythonのfor文にて繰り返しを処理しているので、結果はそのあたりの経過時間も含まれたものになっている。
 レコードは本を想定した。タイトル、著者名、ISBN13を想定した長さ13のランダム文字列、発刊日(datetime)、評価(intで0-5)をカラムとして持つ。

 MongoDBへの書き込みは以下のように。
for book in books:
book_collection.insert(book)

 MySQLへは以下。なおテーブルはInnoDBで作成してある。
add_book = ("INSERT INTO books "
"(title, author, isbn_dummy, publishedDate, rate) "
"VALUES (%(title)s, %(author)s, %(isbn_dummy)s, %(publishedDate)s, %(rate)s)")
for book in books:
cursor.execute(add_book, book)
cnx.commit()


 MongoDBへのクエリとデータ取得。
c = book_collection.find({"author":"Albert Camus"}).sort("rate").skip(1000).limit(100)
camus = [x for x in c]
c = book_collection.find({"author":"Dostoevsky"}).sort("publishedDate").skip(1000).limit(100)
dostoevsky = [x for x in c]
c = book_collection.find({"author":"Herman Hesse", "rate":3}).sort("publishedDate").skip(0).limit(100)
hesse = [x for x in c]
c = book_collection.find({"author":{"$in":["Thomas Man", "William Golding"]}}).sort("publishedDate").skip(0).limit(100)
man_and_w = [x for x in c]

 MySQLへのクエリとデータ取得。
q = "SELECT * FROM books WHERE author = 'Albert Camus' ORDER BY rate LIMIT 100 OFFSET 1000"
cursor.execute(q)
camus = [x for x in cursor]
q = "SELECT * FROM books WHERE author = 'Dostoevsky' ORDER BY publishedDate LIMIT 100 OFFSET 1000"
cursor.execute(q)
dostoevsky = [x for x in cursor]
q = "SELECT * FROM books WHERE author = 'Herman Hesse' AND rate = 3 ORDER BY publishedDate LIMIT 100 OFFSET 0"
cursor.execute(q)
hesse = [x for x in cursor]
q = "SELECT * FROM books WHERE author IN ('Thomas Man', 'William Golding') ORDER BY publishedDate LIMIT 100 Ocursor.execute(q)
man_and_w = [x for x in cursor]


結果
1411091928136.jpg
 データの書き込みではMongoDBが5倍強ほど速かった。一方でクエリによるデータ取得ではMySQLのほうがすぐれていたが、1.3倍程度といったところか。
 ちょっと適当にやった試験なので真面目に結果を捉えるのもアレだが考えてみる。データ書き込みが取得に比べて極端に少ないケースなら、今回の結果からMySQLを使ったほうがいいだろう。一方でそこそこ書き込みが多いケースではMongoDBを使ったほうがよさそう。データストア選定のためにデータの書き込み命令と取得命令の比率と消費時間の関係(insert / select -- cost)を図にしたいが、クエリが適当だったのであまり意味をなさなそう。

 もうちょい環境が整えられればMongoDBをスケールさせたときの効果も試してみたい。使用したコードは改良してのちのち使うかもしれないのでzip化してまとめた。
to use
> python compare.py

PyMongo: http://api.mongodb.org/python/current/index.html
MySQL Connector: http://dev.mysql.com/doc/connector-python/en/index.html

IronPython: MongoDBを使う

                
 Pythonの.NET Framework開発であるIronPython。pystoneで計測するとCPythonを超えるパフォーマンスを出したりしながらも、.NET Frameworkで用意されたAPIを利用できるので、GUIアプリなどを即席で作ったりするのにけっこう使える。今回はMongoDBをIronPythonからいじってみる。


 CPythonで動かしていたアプリケーションをIronPythonに移そうとしたら、エラーが出て動かなかった。原因はMongoDBに接続するために使っていたPythonモジュールのPyMongo。調べてみるとPyMongoはIronPythonに対応していない。ここで出てくる選択肢は自分で開発するか諦めるか。しかしIronPythonならもう一つの手が出てくる、C#や.NET Framework向けに開発されたドライバ使えばいいじゃない。というわけでIronPythonからMongoDBに接続するためのドライバとしてC# Driverを使う。

 C#コードを参考に、IronPythonでドキュメント(RDBでいうところのレコード)の挿入、検索、削除をやってみる。
http://www.codeproject.com/Articles/273145/Using-MongoDB-with-the-Official-Csharp-Driver
不明瞭なところは公式ドキュメントを参考にしながら。
http://api.mongodb.org/csharp/current/

 ドライバのZIPを落として解凍し、Pythonコードと同じディレクトリにMongoDB.Driver.dllとMongoDB.Bson.dllを置いて、以下のようにコードを書けばMongoDBに接続できる(もちろんMongoDBを立ち上げた状態で)。
import clr
clr.AddReference("MongoDB.Bson")
clr.AddReference("MongoDB.Driver")

import MongoDB.Bson
import MongoDB.Driver


 ここでコーディングを容易にするため、補完機能を使う準備をする(しなくてもいい)。コーディングに使うIDEはSharpDevelop 4.4。Pythonプロジェクトを作成して、プロジェクトウィンドウの参照設定にMongoDB.Driver.dllとMongoDB.Bson.dllを追加。MongoDB.Driver.xmlとMongoDB.Bson.xmlも同じディレクトリにコピーしておく。これで補完機能が効くようになる。
1407021654079.png


 改めて下記のコードを。
import clr
clr.AddReference("MongoDB.Bson")
clr.AddReference("MongoDB.Driver")

import MongoDB.Bson
import MongoDB.Driver

これはDLLファイルへの参照を行ったあとで、その機能が使えるようにするためPythonのいつもどおりのimportを行っている。

 続いてデータベースへの接続をする。MongoDBをローカルで、ポートも初期設定で動かしていれば、とくにパラメータを渡すことなく接続できる。
client = MongoDB.Driver.MongoClient()
server = client.GetServer()
db = server.GetDatabase("test")
collection = db.GetCollection("foos")


 挿入をする。今回用意したC# Driverではドキュメントを、.NET Frameworkのディクショナリで表している。これはIronPythonで用意されているディクショナリを使ってもOK。なのでPythonの辞書型でドキュメントとして保存するデータを書く。
# insertion
for p in xrange(10):
foo = MongoDB.Bson.BsonDocument({"x":p, "y":p})
#foo = MongoDB.Bson.BsonDocument()
#foo["x"] = p
#foo["y"] = p
collection.Insert(foo)


 検索をする。QueryDocumentを使って条件を指定できる。
# no filter query
q = MongoDB.Driver.QueryDocument()
cols = collection.Find(q)
for col in cols:
print "no filter", col

# y==0
q.Add("y", 0)
cols = collection.Find(q)
for col in cols:
print "y==0", col

# y!=0
q = MongoDB.Driver.QueryDocument()
q.Add(MongoDB.Driver.Builders.Query.NE("y", 0))
cols = collection.Find(q)
for col in cols:
print "y!=0", col


 削除する。
collection.RemoveAll()
print collection.Count()


 C#のコードから型指定などを省けば、C#ドライバとして用意されていたものがほぼそのまま使える。
http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-csharp-driver/#getting-started-with-csharp-driver

 というわけでPythonのサードパーティモジュールが使えないような場合においても、C#向け、あるいは.NET Framework向けとされるライブラリを探して使えば抱えていた問題が解決される。意外と使えてしまうIronPython。
プロフィール

h

Author:h

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

検索フォーム
Amazon