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モードで無い場合よりも、"ある程度"向上するだけ。という事かなと思いました。

2011年3月29日火曜日

PythonからRedisに大量データをsetしてみた

前回memcachedを試しましたが、最近ではredisというKVSがあるようなのでredisで実験してみました。installについて言うと何のダウンロードしてmakeするだけで問題無く完了。Python用ドライバも何の問題も無くinstall出来ましたので割愛します。(OSはFedora13)

redisの起動は以下です。
./redis-server &

Pythonのコードは以下の通りです。いつものようにCSVファイルから大量のネタを読み込み、100万件のsetを行います。
---------------------------------------------------------
#!/usr/bin/env python
import csv
import redis
import time

if __name__ == '__main__':
    cnt=0
    start=time.time()
    r = redis.Redis()
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        r.set(str(row[0]),row[1])
    print time.time()-start
---------------------------------------------------------
実行してみると、100万件のsetに112sかかりました。

尚、mset()を使用した場合の結果は以下です。
---------------------------------------------------------
#!/usr/bin/env python
import csv
import redis
import time

if __name__ == '__main__':
    cnt=0
    d={}
    start=time.time()
    r = redis.Redis()
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        d[str(row[0])] = row[1]
        cnt = cnt+1
        if cnt % 1000 == 0:
            r.mset(d)
            d={}
    print time.time()-start
---------------------------------------------------------
これは約10sで完了しました。今までの自己ベスト!です。

redisはmemcachedと違ってクライアントツールが入っていてsqlplusとかpsqlとかに慣れてる人にはうれしい限りです。また大量のコマンドが用意されていたり、レプリケーションが出来たり、データの保存(=DISKへの同期)が簡単に出来たりと、memcachedに満足出来なかった人には使ってみる価値がありそうな気がしました。

HTTPHeaderの"Server"をいろいろ確認してみた (NYダウ編)

HTTPHeaderのServerをNYダウを構成している銘柄で確認してみました。
前回と同じく、英語の社名でgoogleで検索し、1位に表示されたものを確認してます。

アルコア   Microsoft-IIS/6.0
アメリカン・エキスプレス IBM_HTTP_Server
ボーイング SUN-ONE-Web-Server/6.1
バンク・オブ・アメリカ SUN-ONE-Web-Server/6.1
キャタピラー IBM_HTTP_Server
シスコシステムズ Apache/2.0
シェブロン     Microsoft-IIS/6.0
デュポン     Apache
ウォルト・ディズニー・カンパニー Microsoft-IIS/6.0
ゼネラル・エレクトリック  Apache/2.2.14(Unix) mod_jk/1.2.25 mod_ssl/2.2.14 openssl/0.9.8o
ホームデポ IBM_HTTP_Server/2.0.47.1-PK65782 Apache/2.0.47(Unix)
ヒューレット・パッカード Apache
アイ・ビー・エム IBM_HTTP_Server
インテル IA Web Server
ジョンソン・エンド・ジョンソン IBM_HTTP_Server
JPモルガン・チェース JPMC1.0
クラフト・フーズ Microsoft-IIS/6.0
コカ・コーラ IBM_HTTP_Server
マクドナルド Apache/2.0.54
スリーエム IBM_HTTP_Server
メルク Apache
マイクロソフト Microsoft-IIS/7.5
ファイザー  "-"
P&G Apache
エーティーアンドティー Apache
トラベーズ Microsoft-IIS/6.0
ユナイテッド・テクノロジーズ Microsoft-IIS/6.0
ベライゾン・コミュニケーションズ Microsoft-IIS/6.0
ウォルマート・ストアーズ Apache/2.2.15
エクソンモービル Microsoft-IIS/6.0

Microsoftが9社で、IBM社が7社となりました。
この数字は日本よりも多そうです。まあNYダウが30社という比較的少ない銘柄数で構成されている関係でTOPIXの構成銘柄よりもかなり大きい会社が多く、単純な比較は難しそうですが。しかし、Server名を隠しているのはファイザー社のみであり、その他の会社は全く隠していません。こうなると、私が監査で指摘されたServerは隠した方が良いというのはそもそも日本のセキュリティ監査する人だけが言ってるの?と疑問に思えてしまいます。

気になったのは以下です。
インテル・・・IA Web Server でした。確かに同社はいろんなハードベンダと親密なので特定の企業の商品を使うのはまずそうですが。。。"Apache"とかでいいんじゃないのかな。
マイクロソフト・・・Microsoft-IIS/7.5 でした。 バージョンは最新?っぽいです。同社は自社製品の最新版を自社のHPに使用しており、まるで、他社に最新バージョンにあげるように催促している?ようにさえ感じます。

繰り返しになりますが、Server名を隠している企業は1社のみでした。あまり気にしなくていいって事なんでしょうね。。

2011年3月26日土曜日

Pythonからmemcachedに大量データをsetしてみた

Pythonからmemcachedに対して大量データをsetしてみました。
OSはFedora13です。

①memcachedのinstall
yum install memcached

②memcachedの起動
memcached -d
...因みにrootユーザでは起動出来ません。

③Python用ドライバのinstall
easy_install python-memcached

これで準備完了です。

次に以下のようなコードを実行します。

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

if __name__ == '__main__':
    start=time.time()
    mc = memcache.Client(['localhost:11211'])
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        mc.set(str(row[0]),row[1])
    print time.time()-start
--------------------------------------------------------------

実行したところ、100万行のsetが完了するのに、130sかかりました。

期待したほどのパフォーマンスは出ませんでした。
memcachedという名の通り、DISKへのアクセスは全く発生しないはず・・・。なんですが。

因みに、、今までの実行結果を振り返ると、
Java+PostgreSQL → 130s
Java+Sqlite3 → 19s
Python + Sqlite3 → 28s

PythonとJavaを速度比較してもあまり意味は無いかも知れませんが、上記からはmemcachedとPostgreSQLの速度はほとんど変わらないという事になってしまいます。これはどういう事でしょうか?

私は、クライアントアプリとmemcachedプロセス間の通信がオーバーヘッドになっているものと考え、上記ソースを以下のように修正し、1000件分まとめてsetするようにしました。

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

if __name__ == '__main__':
    cnt=0
    d={}
    start=time.time()
    mc = memcache.Client(['localhost:11211'])
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        d[str(row[0])] = row[1]
        cnt = cnt+1
        if cnt % 1000 == 0:
            mc.set_multi(d)
            d={}
            cnt=0
    print time.time()-start
--------------------------------------------------------------


上記ソースの場合には100万件のsetに47s程度となり、かなり高速化されました。

自身の経験ではJavaでのバッチ更新(addbatch)とかは使った事は無いのですが、大量の更新事にはかなり高速化に寄与しそうですね。また、SQLiteやH2はプロセス間通信が無い為、大量更新事には非常に高速に動作するというのも納得です。