パフォーマンスとメンテナンス性で揺れる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で書いていた頃よりは、メンテナンスし易いはずだ。
それから…、コードを大きく変えたときはパフォーマンスをきちんと確認しなくてはいけないと、反省。。