2011年4月22日金曜日

PythonからPostgreSQLに大量データのinsert(その2)

以前、PythonのPostgreSQL用のドライバがうまくインストール出来ないと記載しましたが、python-develが入って無かっただけっぽいです。

yum install python-devel

で、
① ocpgdb1.0.3
② psycopg2-2.4
③ PyGreSQL-4.0
④ pg8000-1.08
の全てのドライバがinstall可能でした。
上記はPython2.6及び、2.7で確認しましたが、2.7の場合は④のインストールがいじらないとうまくいきませんでした。(詳細はここに記載されています)

で、今回の趣旨はPythonからのBulkInsertを試してみようというものです。
driverですが、前回は④を使いましたが、今回は②psycopg2を使ってみました。
事前に、
CREATE TABLE hash ( id text PRIMARY KEY , value text );
でhashテーブルというのを作っています。

ソースは以下みたいな感じです。

------ bulk insertする実行例 ------
#!/usr/bin/env python
import csv
import psycopg2
import time

if __name__ == '__main__':
    start = time.time()
    c = psycopg2.connect(host="localhost",user="postgres",password="xxxx",database="testdb")
    i = 0
    l = []
    cur = c.cursor()
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        id = row[0]
        value = row[1]
        t = (id,value)
        l.append(t)
        i += 1
        if i % 10000 == 0:
               cur.executemany("insert into hash values(%s,%s)",l)
               c.commit()
               cur = c.cursor()
               l = []
        c.commit()
    c.close()
    print time.time()-start

上記を実行すると100万件のinsertに大体200sかかりました。

因みに1件ずつinsertするのは以下のような感じです。

------ bulk insertしない実行例 ------
#!/usr/bin/env python
import csv
import psycopg2
import time

if __name__ == '__main__':
    start = time.time()
    c = psycopg2.connect(host="localhost",user="postgres",password="xxxx",database="testdb")
    i = 0
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        id = row[0]
        value = row[1]
        t = (id,value)
        cur = c.cursor()
        cur.execute("insert into hash values(%s,%s)",t)
        i += 1
        if i % 10000 == 0:
              c.commit()
    c.commit()
    c.close()
    print time.time()-start

これを実行すると100万件のinsertに220sかかりました。
executemany()を使用してもパフォーマンス改善にはさしてつながっていません。しかし、言い方を変えると20sも改善しています。これは通信のオーバヘッド以上に処理に時間がかかっている為、改善効果が大きくは見えないという事でしょう。

前回のpg8000で実施したテストよりもだいぶ高速だった為、pg8000でももう一度試してみました。上記のソースで、
・import psycopg2のところをimport pg8000とする
・psycopg2.connect のところを pg8000.dbapi.connectとする
事でつながるようになります。結果として以前やった結果と変わらずにbulkinsertを使おうが使うまいが、1000s以上かかってしまいます。理由は全く不明ですが、バインド変数とかをpg8000だとうまく使えて無いのかな・・・?

<まとめ>
・fedoraにpostgresqlのドライバをinstallする際はpython-develを入れるのを忘れずに
・psycopg2でbulkinsertの効果を確認したが、さしたる改善効果は無かった。
・pg8000とpsycopg2で大量insertのパフォーマンスを比較すると、psycopg2の方が圧倒的に早い。

2011年4月16日土曜日

PythonからSqlite3に大量データをinsertしてみた(その2)

PythonからSqliteに大量データをinsertする際に1件ずつinsertを行うのでは無く、Bulkinsertを行ったら、どの程度高速化されるかを確認しました。Pythonに組み込まれているsqlite3クラスにはexecutemany()というメソッドが実装されていて簡単でした。

事前にsqliteでtestdbというDBを作っておいて、
sqlite> CREATE TABLE hash ( id INTEGER PRIMARY KEY , value TEXT );
でhashというテーブルを作成しておきます。
その後、neta.csvというファイルの中身をhashテーブルにinsertしていきます。

------------------------------------------------------------------------------------------
#!/usr/bin/env python
import csv
import sqlite3
import time

if __name__ == '__main__':
    start = time.time()
    c = sqlite3.connect('./testdb')
    i = 0
    l = []
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        id = row[0]
        value = row[1]
        t = (id,value)
        l.append(t)
        i += 1
        if i % 200000 == 0:
            c.executemany('insert into hash (id,value) values(?,?)',l)
            c.commit()
            l = []
    c.commit()
    c.close()
    print  time.time()-start
------------------------------------------------------------------------------------------

上記を実行したところ、13sで100万件のinsertが完了しました。
以前、1件ずつやった場合だと、27sだったので約2倍の高速化につながりました。

2011年4月9日土曜日

PostgreSQL9.0.3 のpsqlが使えなくてがっかりした

最近PostgreSQLを使ってなかったので、最新版(9.0.3)をFedora13(32bit)にinstallしてみました。尚、PostgreSQLのlinux版はEnterpriseDB社のページからダウンロードするように促される為、そこからファイルを落としてインストーラーを実行します。

インストール自体は何の問題がありませんでしたが、createdbを実行した後にpsqlコマンドにて軽く接続しようとすると以下のエラーが出て、全くつながりません。
--------------------------------------
-bash-4.1$ psql testdb
Password:
psql (9.0.3)
Type "help" for help.

Cannot read termcap database;
using dumb terminal settings.
Aborted (core dumped)
--------------------------------------

はっ?

何か環境設定が悪いのかなと思ったが、特に悪いところは見当たらない。
ので調べると、どうやら既知のバグのようである。

BUG #5807: psql fails to launch with "Cannot read termcap database; using dumb terminal settings. Aborted"

尚、対象のBUGの環境はUbuntuとだけ記載されている。9.0.2から中身が変わっていて出るようになったのだろうとの事。

また、この話題ではPostgreSQLのMLは盛り上がって無かった。
Redhat とか CentOSだと問題の無い話なんでしょうか。

肝心の解決方法はさらっととしか記載が無かったし、9.0.3でも改善していないので少しがっかりですが、

LD_PRELOAD=/lib/libreadline.so.6

を追加したらうまくいく・・・との事。
実際これでうまくいきました。

しかし、PostgreSQLのMLの議論では以下のようにありました。

There was a change in 9.0.2 to use libedit instead of readline for this (readline is GPL, which is not acceptable to some people who wish to use Postgres in other products).

つまり、9.0.2からはreadlineを使わないように修正したんだけど、回避方法はreadlineを無理やり使おう・・・という事のように読み取れます。

全般的に何だかなーというところです。

2011年4月3日日曜日

PythonでMongoDBに大量のデータをinsertしてみた(その2)

以前MongoDBに大量データをinsertしてみましたが、まとめてinsertした場合、どの程度効果があるのか確認してみました。MongoDBは最新版のv1.8.0です。

まず、MongoDBを起動します。

./mongod --dbpath datapath --logpath logpath --fork

PyMongoというPython用ドライバをinstallする際に以下のようなエラーになりましたが、一応インストール自体は終わったので問題無かな・・と思います。
**************************************************************
WARNING: The pymongo._cmessage extension module could not
be compiled. No C extensions are essential for PyMongo to run,
although they do result in significant speed improvements.

Above is the ouput showing how the compilation failed.
**************************************************************

その後、以下のPythonコードを実行します。いつものように100万行のCSVファイルを1件ずつ読み込み、CSVに記載してある2列をそれぞれ、id列、value列として、hashコレクションにinsertしていきます。
-----------------------------------------------------------
#!/usr/bin/env python
import csv
import pymongo
import time

if __name__ == '__main__':
    start = time.time()
    L=[]
    cnt = 0
    c = pymongo.Connection()
    db = c.testdb
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
  D = {}
  D["id"] = row[0]
  D["value"] = row[1]
  L.append(D)
  cnt += 1
  if cnt % 1000 == 0:
   db.hash.insert(L)
   D={}
   L=[]
  print time.time()-start
-----------------------------------------------------------
実行時間は96sでした。

また、今回改めて1件ずつinsertするやり方も確認しました。
-----------------------------------------------------------
#!/usr/bin/env python
import csv
import pymongo
import time

if __name__ == '__main__':
    start = time.time()
    D = {}
    c = pymongo.Connection()
    db = c.testdb
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
  D["id"] = row[0]
  D["value"] = row[1]
  db.hash.insert(D)
  D={}
 print time.time()-start
-----------------------------------------------------------
実行時間は230sでした。

つまり、1000件ずつまとめてinsertする方が2倍以上高速に動作しているようです。

因みにですが、MongoDBv1.8からジャーナルモードをサポートしているようです。RDBのように、変更履歴を先にLogファイルとして書き出し、耐障害性の向上を目指しているようです。そうすると、パフォーマンスは落ちるのだろうなーと思って実験してみました。

ジャーナルモードをONとするには起動の際にオプションで指定します。
./mongod --dbpath datapath --logpath logpath --journal --fork

結果だけ言うと、パフォーマンスは1件ずつinsertした場合でも1000件まとめてinsertした場合でもジャーナルモードで無い場合とほとんど変化ありませんでした。どうもMongoDBでの実装ではinsert毎にDISKに書き出しているようでは無いようです。

---以下、manualの抜粋
MongoDB performs group commits (batch commits) when using the --journal option. This means that a series of operations over many milliseconds are committed all at once. This is done to achieve high performance.

つまり、、journalモードと言ってもinsertしたデータが必ず保護されるという訳では無くて、耐障害性がjournalモードで無い場合よりも、"ある程度"向上するだけ。という事かなと思いました。