今日もプログラミング

IT技術とかプログラミングのこととか特にJavaを中心に書いていきます

パフォーマンスとメンテナンス性で揺れるembulk-output-oracle

embulk-output-oracle

embulkとはバルクでのデータ転送ツールだ。

embulk-output-oracleはそのプラグインの1つで、Oracleにデータをロードするためのものだ。

 

OCIの利用

OCIとは、Oracle Call Interfaceのことで、要するにOracleのネイティブライブラリAPIだ。

embulk-output-oracleは、JDBC経由でINSERT文によりデータをロードしていたが、これでは遅い。

そこで、OCIのダイレクトロード機能SQL*Loaderのようなもの)を利用することにより高速化することにした。

JDBCを使うかOCIを使うかは選べるようになっている)

 

これでずいぶん速くなったのだが…、ネイティブライブラリを使うためにCとかJNIのコードを書いたり、しかもCはWindows版とLinux版とを用意したりして、メンテナンス性は悪化してしまった。

 

JNIをjnr-ffi

Javaからネイティブライブラリを呼び出す標準的な手法はJNIだが、jnr-ffiという便利なフレームワークもある。

これを使うと、CとかJNIのコードを書かなくても、Javaから直接的な感じでネイティブライブラリを呼べるのだ!(使い方はこちら

JNIをjnr-ffiに置き換えることにより、Cのコードが無くなり、かなりすっきりした。

 

jnr-ffi化によるパフォーマンスの低下

久しぶりにembulk-output-oracleのパフォーマンスを測ってみると、なんか遅くなっている?

ので、分析してみた。

 

OCIによるロードは、おおざっぱに言うと

① ロードする値を設定 (OCIDirPathColArrayEntrySet)

Oracleにロード (OCIDirPathLoadStream)

の手順である。

 

試しに13列×2億行のデータをロードしてみたところ、

① 合計10分くらい

② 合計8分くらい

という結果だった。

②の処理は、実際にデータベースにアクセスするし、SQL*Loaderの処理時間と比べてみても妥当な感じだ。

 

しかし、①の処理は、単にメモリ上で値を設定しているだけのはずなのに、ずいぶん時間が掛かっている!

OCIDirPathColArrayEntrySetは列数×行数回呼び出すので、26億回の呼び出しだ。

jnr-ffiは軽いと思うが、さすがに26億回も呼び出すとかなりのオーバーヘッドになるのだろう。

 

やはり一部をC+JNIに戻そうか

で、試しに一部をC+JNIに戻して測ってみた。

具体的には、OCIDirPathColArrayEntrySetをまとめて呼び出すCのコードを書いてみた。

Javaから1万行分のデータをそいつに渡し、そいつがOCIDirPathColArrayEntrySetを13万回呼び出す。

これにより、jnr-ffi経由の呼び出しは13万分の1の2万回に減る。

 

実際測ってみると、①の処理時間が10分くらいから2分くらいに大幅に短縮された。

全体で見ても、処理時間が半分くらいになる。

jnr-ffi化前の水準だ。

 

まとめ

やはり遅いのはロードツールとしてはまずいので、部分的にCに戻そうと思う。

それでも、OCIまわりを全てCで書いていた頃よりは、メンテナンスし易いはずだ。

 

それから…、コードを大きく変えたときはパフォーマンスをきちんと確認しなくてはいけないと、反省。。