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はプロセス間通信が無い為、大量更新事には非常に高速に動作するというのも納得です。

2011年3月17日木曜日

PythonからCouchDBに大量データをInsertしようとした

PythonからCouchDBに大量データをinsertしようとしてみました。
しようとした…としているのは結論から言うと出来なかった為です。

まず、マニュアルに沿ってCouchDBのinstallをしました。
(OSはFedora13です)

// yumで必要なパッケージをインストール
yum install erlang subversion icu libicu-devel js js-devel libcurl-devel libtool

// ソースの取得
svn checkout http://svn.apache.org/repos/asf/couchdb/trunk couchdb

// CouchDBのインストール
cd couchdb
./bootstrap
./configure
make install

// 起動
couchdb
---------------------------------------------------------------------
Apache CouchDB 1.2.0a1078467 (LogLevel=info) is starting.
Apache CouchDB has started. Time to relax.
---------------------------------------------------------------------
これで起動完了です。"Time to relax"なんて洒落てますよね。

次にCouchDBのPython用のドライバのinstallを実施しました。
easy_install couchdb

またBrowserからアクセスして新しいDBを作成します。
ここではtestdbを作成しました。

ここまでは何の問題も無く終了。

次に以下のようなソースを実行します。
基本的にはMongoDBで使ったものとほとんど一緒です。
以前もやったようにCSVネタとして100万件のneta.csvを用意しておきます。

----------------------------------------------------------------
#!/usr/bin/env python

import csv
import couchdb
import time

if __name__ == '__main__':
    start = time.time()
    server=couchdb.Server()
    db = server['testdb']
    reader = csv.reader(open("./neta.csv"))
    for row in reader:
        _id = row[0]
        value = row[1]
        t = {"_id":_id , "value":value}
        db.save(t)
    print time.time()-start
----------------------------------------------------------------

上記を実行すると我慢出来ない位遅い結果となりました。
大体ですが、100件save()するのに8〜9sかかります。
つまり100万件saveするには丸一日かかってしまいます。

尚、CouchDBはデフォルトのままで使用しており、パラメータチューニング?等は全く行っていません。なんで、こんなに遅いんでしょうか?正確には不明ですが、save()の度にcommit()に近い処理が行われてる為なのかな・・・と思い色々調査しましたが、原因が分かりませんでした。Pythonのコードの書き方が原因だと思うんですが。。。


以下は気づいた点です。


・couchdb.Server()の設定値
Pythonドライバのマニュアルではcouchdb.Serverを実行する際に
 full_commit=Truesession=None
がデフォルトとなっているようだが、上記二つの値の正確な意味が不明。このあたりの仕組みが理解出来れば何とかなるのでは・・・と思ったが分からず。

・saveをまとめてやる
save()をまとめてやれれば、パフォーマンス改善しそう…ですが、実装方法が分かりませんでした。


・delayed_commits
/usr/local/etc/couchdb/default.ini の記述をよく見ると、

delayed_commits = true ; set this to false to ensure an fsync before 201 Created is returned
という一行があり、defaultではsave()毎に実行するであろうfsync()を待たずに動作していると思われる。




尚、CouchDBにはその他DBMSやMongoDBと違ってコマンドラインからselectしたりupdateしたりするクライアントが無く、全てFutonと呼ばれるGUIからしか操作する事になります。また、単純なselectに該当するものが簡単には出来ない為、RDB慣れしてる者にとっては非常にとっつきにくい印象を受けます。


<まとめ>
・CouchDBのインストールはスムーズに完了
・Pythonのコード自体も非常にシンプルに実装可能
・データ挿入のパフォーマンスは???測定不能。Pythonのコーディングの問題??
・RDBに慣れてるものにとっては何だかとっつきにくい

全然RELAX出来ませんでした。むしろいらいらします。


慣れの問題ですかね???
個人的にはMongoDBの方を推奨ですかね。。

2011年3月14日月曜日

Youtubeの動画をFedoraに取り込んでみた

日頃からYoutubeの動画をよく見ています。しかし、
・家のネット環境だと夜間はつながりにくい
・同じ動画を何度も見る
ので、Youtube動画をローカルPC(Linux)に取り込みたいと以前から思っていました。

ふと見た日経Linuxに"clive"というソフトが紹介されていたので試してみました。

記事の内容通りに設定を進めてみます。
尚、以下はFedora13でやってみた結果です。

以下をinstall…
yum install perl-Getopt-ArgvFile
yum install perl-Class-Singleton
yum install perl-HTML-TokeParser-Simple
yum install perl-Digest-SHA
yum install perl-WWW-Curl
yum install perl-Config-Tiny
yum install perl-BerkeleyDB
yum install perl-TermReadKey
yum install perl-ExtUtils-MakeMaker

以下も必要…と書いていましたが、既に入っていた為に割愛しました。
yum install perl-version
yum install perl-URI

以下を実行。
perl Makefile.PL

すると、以下のメッセージが出力されましたが、とりあえずMakeファイルは作成されました。

Checking if your kit is complete...
Looks good
Warning: prerequisite JSON::XS 2.3 not found.
Writing Makefile for clive

あとは以下二つを実行すればどこかにcliveコマンドが作成されてできあがりです。
make
make install

しかし、cliveでいざ取り込もうとすると、エラーとなりました。
clive URL

error: specify path to quvi(1) command with --quvi at /usr/local/bin/clive line 99
    main::init() called at /usr/local/bin/clive line 43
    main::main() called at /usr/local/bin/clive line 40

quviってものが原因だろうと思い、色々とやってみましたが、断念しました。
残念。。。


clive以外のソフトを探してみると、youtube-dlというものを発見しました。
で、以下を実行すればとりあえず簡単に動画のダウンロードまでは完了しました。

yum install youtube-dl
youtube-dl URL

とすればURLに指定されたYoutube動画が.flvファイルとして保存されます。

ですが、、
今度は実際に.flvファイルをtotemというソフトで実行しようとすると以下のエラーとなりました。

(totem:3138): Totem-WARNING **: Failed to create dbus proxy for org.gnome.SettingsDaemon: Could not get owner of name 'org.gnome.SettingsDaemon': no such name
** Message: Error: Your GStreamer installation is missing a plug-in.


これは、他のblogを参考にさせてもらいましたが、以下で回避可能です。
rpm -Uvh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm
rpm -Uvh http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm
yum install gstreamer-ffmpeg gstreamer-plugins-bad gstreamer-plugins-ugly

上記でtotemでローカルに保存した.flvファイルをtotemで見れるようになりました。



まとめ

・Fedoraにcliveを入れようとしたが、うまくインストール出来なかった。しかし、youtube-dlはインストール出来て、Youtubeの動画ダウンロードも出来た。

・ダウンロードした.flvファイルをtotemで見ようとするとそのままの状態では見れない為、追加でinstallする必要がある。

尚、私の環境で使っているとtotemがごく稀にcoredump(異常終了)します。愛嬌ってやつですね。

2011年3月5日土曜日

H2に大量データをinsertしてみた

H2 databaseというDBで大量データinsertをやってみました。
H2のホームページを見ているとDerbyよりも高機能/高速である事を売りにしています。

H2にはDerbyと同様に組込モードとサーバモードの両方が実装されていますが、以下の確認は組込モードのみで実施おり、バージョンは1.3.152です。順序は以下のとおりです。
・H2のページからダウンロード
・CLASSPATHを設定
・ java -cp h2*.jar org.h2.tools.Shell を実行する
(もしくは、 java -cp h2*.jar org.h2.tools.Server)
・コマンドラインからテーブルの作成を実施する
create table hash ( id integer PRIMARY KEY , value varchar(40));
・insert用のCSVネタを準備する
・以下のアプリを実行する
(赤字が修正した箇所です)

import java.sql.*;
import java.io.*;
import java.util.Date.*;
public class H2ins {
        public static void main (String args[]) {
                Connection db = null;
                PreparedStatement ps = null;
                String sql = "insert into hash values(?,?)";
                String url = "jdbc:h2:testdb";
                String usr = "sa";
                String pwd = "";
                BufferedReader br = null;
                int i = 0;
                try {
                        Class.forName("org.h2.Driver");
                        db = DriverManager.getConnection(url,usr,pwd);
                        db.setAutoCommit(false);
                        ps = db.prepareStatement(sql);
                } catch (Exception ex) {
                        ex.printStackTrace();
                }
                java.util.Date d1 = new java.util.Date();
                System.out.println(d1);
                try{
                        br = new BufferedReader(new InputStreamReader(new FileInputStream("neta.csv")));
                        String line;
                        while( (line = br.readLine()) != null ){
                                i++;
                                String[] col = line.split(",");
                                int id = new Integer(col[0]);
                                String value = col[1];
                                ps.setInt(1,id);
                                ps.setString(2,value);
                                ps.executeUpdate();
                                if( i % 10000 == 0) {
                                        System.out.println(i);
                                        db.commit();
                                }
                        }
                        // データベース切断
                        db.commit();
                        ps.close();
                        db.close();
                        br.close();
                } catch (Exception ex) {
                        ex.printStackTrace();
                }
                java.util.Date d2 = new java.util.Date();
                System.out.println(d2);
        }
 }

やってみると約20sで100万件のinsertが完了します。高速だと言う謳い文句は間違っていないようです。

またH2はSqliteを意識しているようで、manual上のAndroidでの実装の箇所でSqliteでは無くH2を使う理由として以下を挙げています。(抜粋)
・参照制約+チェック制約
・豊富なデータTypeとSQL
・他DBMSの互換モード
・完全なUnicodeのサポート
・ユーザ定義関数+トリガー
・全文検索機能
・複数コネクションの許容
・データベースの暗号化

Sqliteの最新では全文検索機能等は存在しているんで、あくまでAndroidでのSqlite実装が古いバージョンで行われているという事なんでしょうか。。しかし、組込モードで複数コネクションの許容とかしてもしょうがないんじゃないかなーー。

何点か補足します。

・ Derbyでも気になりましたが、DBにConnectするタイミングで若干のタイムラグを感じます。何らかの初期処理(Classのロードとか)で時間がかかっているんでしょうかね。尚、insert時間の測定は純粋にinsertの時間のみを測っている為、Connect/Disconnectの時間は含んでいません。

・java -cp h2*.jar org.h2.tools.Shellで最初にDBの接続情報を入力しますが、Useridが自動的に"sa"となります。
(ShellからUserid/Password無にする方法が分かりませんでした)
java -cp h2*.jar org.h2.tools.Server ではuserid/passwordを無として設定できました。

2011年2月24日木曜日

Derbyに大量データをinsertしてみた

Apache Derbyに大量データをinsertしてみました。
Derbyには組み込みモードとサーバモードがありますが、組み込みモードでのみ確認しました。

Derbyを動かすまでの手順は以下の通り。基本的にはマニュアル通りです。
・Derby10.7.1.1をダウンロードする(現時点での最新)
・DERBY_HOMEを設定する
・$DERBY_HOME/binをPATHに追加
・$DERBY_HOME/lib/derby.jarをCLASSPATHに追加

・ijを実行し、testdbにConnectする
[user@user bin]$ ij
ij バージョン 10.7
ij> CONNECT 'jdbc:derby:testdb;create=true';

・hashテーブルの作成
ij> create table hash ( id integer PRIMARY KEY , value varchar(40));
0 行が挿入/更新/削除されました

下準備はこれで完了です。

毎回同様ですが、100万件のCSVネタを読み込みながらinsertを100万回行います。
以下、赤字が変更点です。
import java.sql.*;
import java.io.*;
import java.util.Date.*;

public class derbyins {
    public static void main (String args[]) {
        Connection db = null;
        PreparedStatement ps = null;
        String sql = "insert into hash values(?,?)";
        String url = "jdbc:derby:testdb";
        BufferedReader br = null;
        int i = 0;
        try {
            Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
            db = DriverManager.getConnection(url);
            db.setAutoCommit(false);
            ps = db.prepareStatement(sql);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        java.util.Date d1 = new java.util.Date();
        System.out.println(d1);

        try{
            br = new BufferedReader(new InputStreamReader(new FileInputStream("neta.csv")));
            String line;
            while( (line = br.readLine()) != null ){
                i++;
                String[] col = line.split(",");
                int id = new Integer(col[0]);
                String value = col[1];
                ps.setInt(1,id);
                ps.setString(2,value);
                ps.executeUpdate();
                if( i % 10000 == 0) {
                    System.out.println(i);
                    db.commit();
                }
            }
            // データベース切断
            db.commit();
            ps.close();
            db.close();
            br.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        java.util.Date d2 = new java.util.Date();
        System.out.println(d2);
    }
 }

100万行insertするのに227sかかりました。SQLite3+JDBCよりもずいぶん遅く、PostgreSQL+JDBCよりも遅い結果となりました。

ApacheDerbyというものは今回初めて知ってさらっと使ってみましたが、非常に手軽に使えるのは間違いありません。Derbyのメリットをあげると以下でしょうか。
・JDBCの品質が高い(はず)
・組み込みモードとサーバモードの2種類で使える
・レプリケーションが標準で搭載されている

しかし、残念ながら日本ではDerbyを使っているという話は少なくともネット上には見受けられません。これは個人的には以下のような理由かなと思います。
・PublicDomainでは無い→Sqliteの方が使用のハードルが低い
・Sqliteの知名度に勝てない
・Javaでしか使えない
・さして高速でも無い

まあ、組み込みとサーバモード両方で使えたり、レプリケーション出来たりしても、私には使う場所が無いですね。全然Sqliteでいいです。

2011年2月17日木曜日

JavaでSqlite3に大量データをinsertしてみた

今回はJavaからsqlite3に大量データをinsertしてみました。
以前と同じですが、条件は再掲します。

まず、sqlite3でテーブルを作ります。
sqlite> CREATE TABLE hash ( id INTEGER PRIMARY KEY , value TEXT );

insertするネタはcsvファイルで
1,356a192b7913b04c54574d18c28d46e6395428ab
2,da4b9237bacccdf19c0760cab7aec4a8359010b0
3,77de68daecd823babbb58edb1c8e14d7106e83bb

1列目は数字で2列目は数字のsha1値です。
これを100万行分用意します。(neta.csv)

次にsqlite3のJDBCを探します。複数ありそうですが、今回はココにあったやつをダウンロードしました。HP上の記載を見るとsqliteは3.6.14がベースとの事です。

次に以前作成したpostgresqlへのinsertするjavaアプリを適度にsqlite用に数行修正しました。変更点は以下、赤字です。

import java.sql.*;
import java.io.*;
import java.util.Date.*;

public class sqliteins {
    public static void main (String args[]) {
        Connection db = null;
        PreparedStatement ps = null;
        String sql = "insert into hash values(?,?)";
        String url = "jdbc:sqlite:testdb";
        BufferedReader br = null;
        int i = 0;
        try {
            Class.forName("org.sqlite.JDBC");
            db = DriverManager.getConnection(url);
            db.setAutoCommit(false);
            ps = db.prepareStatement(sql);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        java.util.Date d1 = new java.util.Date();
        System.out.println(d1);

        try{
            br = new BufferedReader(new InputStreamReader(new FileInputStream("neta.csv")));
            String line;
            while( (line = br.readLine()) != null ){
                i++;
                String[] col = line.split(",");
                int id = new Integer(col[0]);
                String value = col[1];
                    ps.setInt(1,id);
                ps.setString(2,value);
                ps.executeUpdate();
                if( i % 10000 == 0) {
                    System.out.println(i);
                    db.commit();
                }
                }
             // データベース切断
            db.commit();
            ps.close();
            db.close();
            br.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        java.util.Date d2 = new java.util.Date();
        System.out.println(d2);
    }
 }

CLASSPATHを通して実行します。
100万件のinsertの実行時間は39sでした。
因みに、
commitを10万回に1回にした場合→21s
commitを50万回に1回にした場合→19s
とさらに早くなりました。

全く同じコードでもPostgreSQLよりも断然早いです。そもそもPostgreSQLやその他DBMSとSqliteは用途が若干異なる為、比較する意味はあまり無いかもしれませんが、Sqliteはかなりいい感じ。。。ですね。

2011年2月8日火曜日

JavaでPostgreSQLに大量データをinsertしてみた

JAVAからPostgreSQLに例によって大量データをINSERTしてみました。
環境は以下です。
・Fedora13+PostgreSQL9.0 + JDBCも9.0(Type4)+JAVA6
以下のようなコードを作成しました。見苦しいところはお許しを。。

import java.sql.*;
import java.io.*;
import java.util.Date.*;
public class postgresqlins {
public static void main (String args[]) {
            Connection db = null;
            PreparedStatement ps = null; 
            String sql = "insert into hash values(?,?)";
            String url = "jdbc:postgresql:testdb";
            String usr = "postgres";
            String pwd = "postgres";
            BufferedReader br = null;
            int i = 0;
            try {
               Class.forName("org.postgresql.Driver");
               db = DriverManager.getConnection(url, usr, pwd);
               db.setAutoCommit(false);
               ps = db.prepareStatement(sql);
            ) catch (Exception ex) {
               ex.printStackTrace();
            }
            java.util.Date d1 = new java.util.Date();
            System.out.println(d1);
            try{
                 br = new BufferedReader(new(InputStreamReader(new(FileInputStream("neta.csv")));
                String line;
                while( (line = br.readLine()) != null ){
                   i++;
                   String[] col = line.split(",");
                   int id = new Integer(col[0]);
                   String value = col[1];
                   ps.setInt(1,id);
                   ps.setString(2,value);
                   ps.executeUpdate();
                   if( i % 10000 == 0) { 
                        System.out.println(i);
                        db.commit();
                   }
                }
                db.commit();
                ps.close();
                db.close();
                br.close();
           } catch (Exception ex) {
                ex.printStackTrace();
           }
           java.util.Date d2 = new java.util.Date();
           System.out.println(d2);
      }
}

以前と同様にneta.csvという100万件のデータを読み込んで、PostgreSQLにINSERTしています。INSERTにかかった時間は130sでした。
以下は感想。
・JDBCはやっぱり安定している。(Pythonと違って戸惑う事が無し!)
・Pythonのドライバと比較すると、約10倍速い。
・この差は果たして、言語的なものなのか、ドライバの優劣なのか、アプリがおかしいのか?は不明でした。
・ps = db.prepareStatement(sql) のコードをwhileループの中に書くと倍以上遅くなる。構文解析(PARSE)処理が多発するからでしょう。
・Commitタイミングを10万件にしてやってみたが、大差は無かった。

2011年1月24日月曜日

PythonでPostgresqlに大量のデータをINSERTしてみた

Python2.xでPostgresqlに大量データのInsertを試みました。
まず、SqliteやMongodbとかでやったときとの違いとしては選択出来るDriverが多いのです。

①ocpgdb1.0.3
②psycopg2-2.3.2
③PyGreSQL4.0
④pg8000-1.08
ドライバの版数は直近のものです。どれがいい・・というのは特には無さそうです。しかし、Installしてみると、④以外は全てErrorとなりました。コンパイルでエラーになるというのが共通です。何だかなー。因みに環境はFedora13+Python2.6.4+Postgresql9.0です。

サンプルみたいなものはあんまりWeb上には無いので、Manualを読みながらやってみました。

#!/usr/bin/env python
import csv
from pg8000 import DBAPI
import time

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


本当にあってるのか自信が持てないのですが、一応上記で動作しました。
例はCSVからデータを1行ずつ読み込んで100万件Insertするというものです。
しかし、integerの行に対してInsertするとエラーとなった為、テーブルを以下のようにしてid列をTEXT型で作成しました。
create table hash2 ( id TEXT PRIMARY KEY , value TEXT );

100万行のInsertには1145sかかりました。これはSqliteやMongodbと比較するとかなり遅いです。しかし冷静になって考えると1秒で約1000件弱Insertしているので、日頃の仕事で使っているDBと比較しても大差無いのかな、というところです。

以下、補足します。
・性能があがりそうなパラメータを少し変化させたが有意な差は得られませんでした。
 (wal_buffersの値を増やす、synchronous_commitをoffにする・・・等)
・id列のPRIMERY KEYを削除してやってみましたが、やはり性能差はありませんでした。
・Python3.xにはpy-postgresqlというものがあり、ドライバの品質(ドキュメント?)的には一番良さそうな気がしました。しかし、テストアプリを作るところで挫折しました。
(Transactionの実装方式がイマイチ??です)
・DBAPI2.0ではInsertするときもCursorを使うみたいです。内部的にはPreparedStatementみたいな事もしてくれてる?かは不明です。

OSSなので当然なんですが、Pythonのドライバは有志の人が勝手に作っていて、PostgreSQLのコアメンバの人は関わっていないみたいです。コアの人たちが関わってくればもうちょい扱いやすくなるのになーと思いました。

2011年1月15日土曜日

Amazon Kindle3がWifiでつながらずに苦労する

だいぶ前の話になりますが、、、

前から欲しいなーとは思っていた、Amazon Kindle。 バージョン3となり、139ドル!と魅力的な値段に。。。 これは買いだろうと思い購入。このときは3GとかWifiとか気にもしてませんでした。

米国から届いたので早速やってみたんですが、、あれっ?、そもそも本を買ったりするのってどうやるんだろう。。。ネットワークの設定のようなものが出てくるがつながらず。。全くわからん。。。

ここで(SEなのに)3GとかWiFiの意味を初めて知る。
・3G→端末だけでインターネットに接続可能。
・Wifi→端末は無線通信の機能のみ有、ネットワークにつながっているPCやルータ?等が必要。

amazonのサイトをよく見ると、
・3G+Wifiモデル→189ドル
・Wifiモデル→139ドル
となり3GがついてるモデルはWifiのみのモデルよりも高いのです。(今考えると当たり前ですが)

本当に恥ずかしながら無線LANは本で読んだ事はあるが、使った事が無い私。そもそもプロバイダーとかと新規契約する必要あるの?とかマクドとか行かないとつながらないの?とか驚くほどしょうもない事を調べる。。どうやらインターネットに繋がるPCとかがあれば、無線LAN機器を使って接続可能なようだ。

なぜか嫁さんがUSBの無線LANアダプタを持っていたので早速使ってみる。。
だめだ、つながらん。。と来る日も来る日も(夜中に)思考錯誤するが、つながらない。。
そもそもですが、Kindleは入力装置が貧弱でスムーズに入力出来ないので色々試そうとしてもものすごーーく時間がかかってしまいます。

USB型のやつはひょっとしてKindleと相性が悪いのか・・・
という訳でUSB型じゃない無線LANの親機をK'sデンキで購入。。。
これでつながらないならもうあきらめよう・・・

すると、驚くほど簡単につながった。

やった!!!



そこで前から欲しかった英語の技術書を買う。通勤時間にKindleで英語の本を読む。
何だかインテリな気分。。。(半分程度しか理解出来ないのですが。)

1か月程度たった頃、ちょっとしたはずみでKindleに少し力をかけてしまった。そのタイミングで何か鈍い感触。

もしや・・・

という訳でいとも簡単に壊れてしまいました。(電源は入るんですが、何も見えない状態)
壊れた瞬間に私のKindle熱は一瞬で冷めてしまいました。
もう当分、電子書籍には手を出さないようにします。。


                            ↓こんな感じで。。亀裂骨折です。



<まとめ>
・Kindle3は安い。基本的には常時ネット接続するような必要性は無いのでWifiのみで良いと思う。
・Kindle3は目に優しい。普通の本を見ているようなもので、PCを見るよりも目が疲れないです。
・Wifiのみの場合、無線LANの知識が無いとかなり苦戦する。まあ安いUSBのやつでもつながるとは思うんですが、身近で無線LANを使ってない場合はそこそこの無線LAN機器を買った方が無難かも。(そんな事感じたの、私だけかな・・?)
・Kindle3は電子製品なので当然壊れる。壊れた時はショックが大きい。



↓今となってはどうでもいいんですが、K'sで買った無線LAN機器です。
 これはつながりました。