今日もプログラミング

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

embulk-output-oracleを高速化したので使い方についてまとめる

embulk-output-oracleを劇的に高速化した0.2.2がリリースされたので、使い方について書いてみる。

どのくらい速くなったかは改めてまとめる予定だけど、これに沿った感じになると思う。

 

3つの挿入モード

READMEにも書いたけど、embulk-output-oracleには

  • normal
  • direct
  • oci

の3つの挿入モードがあり、insert_methodで指定する。

normal

"normal"は文字通り、通常のINSERT文による挿入。

direct

"direct"は、ダイレクト・パス・インサートにより挿入する。これは、いろいろ制約があるものの通常のINSERTより高速だ(SQL*Loaderに近い)。

ちなみに、INSERT文に/* APPEND_VALUES */ヒントを付けることにより、ダイレクト・パス・インサートにすることができる。

oci

"oci"は、Oracleのネイティブクライアント(Oracle Instant Client)をOCI(Oracle Call Interface)を経由で使ってダイレクト・パス・インサートを実行する。JDBC経由の"direct"よりさらに高速だ。

なお、"oci"を使う場合でも、データベースのメタ情報の取得などにJDBCを使うので、JDBCドライバは必須だ。

 

接続の設定

host、port、database、user、passwordを指定する。

host、port、databaseの代わりに、urlを指定してもよい。

ただし、insert_methodが"oci"の場合は、url指定でもhost、port、databaseは必須だ。OCIでの接続にこれらが必要だからだ。

 

JDBCドライバ

embulk-output-oracleJDBCドライバを同梱していないので、別途ダウンロードする必要がある。

driver_pathに、ドライバのパスを指定する。

動作するバージョンについては、後述。

 

データ型

データ型については、0.2.1のときと変わっていないので、こちらを参照。

 

ymlのサンプル

ymlのサンプルはこんな感じ。

in:
  type: file
  path_prefix: '/data/example.csv'
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    columns:
    - {name: ID, type: string}
    - {name: VARCHAR2_ITEM, type: string}
    - {name: INTEGER_ITEM, type: long}
    - {name: NUMBER_item, type: string}
    - {name: DATE_ITEM, type: timestamp, format: '%Y/%m/%d'}
    - {name: TIMESTAMP_ITEM, type: timestamp, format: '%Y/%m/%d %H:%M:%S'}
out:
    type: oracle
    host: localhost
    database: TESTDB
    user: TEST_USER
    password: test_pw
    table: EXAMPLE
    mode: insert
    insert_method: direct
    driver_path: /drivers/ojdbc7.jar

 

embulk-input-filesplitにより更に高速化

入力がテキストファイルの場合は、embulk-input-filesplitによりマルチスレッドでファイルを読み込むことにより、更に高速化する。

ymlのサンプルはこんな感じ。

in:
  type: filesplit
  path: '/data/example.csv'
  parser:
    charset: UTF-8
    newline: CRLF
    type: csv
    delimiter: ','
    columns:
    - {name: ID, type: string}
    - {name: VARCHAR2_ITEM, type: string}
    - {name: INTEGER_ITEM, type: long}
    - {name: NUMBER_item, type: string}
    - {name: DATE_ITEM, type: timestamp, format: '%Y/%m/%d'}
    - {name: TIMESTAMP_ITEM, type: timestamp, format: '%Y/%m/%d %H:%M:%S'}
out:
    type: oracle
    host: localhost
    database: TESTDB
    user: TEST_USER
    password: test_pw
    table: EXAMPLE
    mode: insert
    insert_method: direct
    driver_path: /drivers/ojdbc7.jar

 

動作環境について

全ての組み合わせで動作確認を行うことはできていないが、とりあえず分かってる範囲で書いてみる。

サーバ側は12cで確認したが、前のバージョンでも動くと思う。たぶん。

normal(通常のINSERT)

12cのJDBCドライバ、11gR2のJDBCドライバでは正常に動作したが、11gR1のJDBCドライバでは以下のエラーが発生した。

Caused by: java.sql.BatchUpdateException: ORA-00928: SELECTキーワードがありません。
    at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:629)
    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:9409)
    at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:211)
    at org.embulk.output.jdbc.StandardBatchInsert.flush(StandardBatchInsert.java:64)

10gのJDBCドライバではそもそも接続できなかった(Java1.4の時代のだし…)。

 

direct(ダイレクト・パス・インサート)

12cのJDBCドライバで以下のエラーが発生。11gR2、11gR1のJDBCドライバでは正常に動作した。

Caused by: java.lang.ArrayIndexOutOfBoundsException
    at java.lang.System.arraycopy(Native Method)
    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:12208)
    at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:246)
    at org.embulk.output.jdbc.StandardBatchInsert.flush(StandardBatchInsert.java:64)

 

oci(OCIによるダイレクト・パス・インサート)

"oci"では、embulk-output-oracle側もネイティブライブラリが必要になる。とりあえずWindows(x68-64)用とLinux(x68-64)用しか同梱していないので…、他の環境の人はここの下にあるビルドスクリプトを参考にご自分でビルドしてください。。

また、"oci"を使うにはOracle Instant Clientをインストールする必要がある。

Windowsの場合はPATH、Linuxの場合はLD_LIBRARY_PATHを設定する。

また、Linuxの場合は

ln -s libclntsh.so.12.1 libclntsh.so
ln -s libocci.so.12.1 libocci.so

も必要。embulk-output-oracleのネイティブライブラリは11gのライブラリを使ってビルドしているので、12cのクライアントを使う人は更に

ln -n libocci.so.12.1 libocci.so.11.1
ln -n libclntsh.so.12.1 libclntsh.so.11.1

も必要。

 

動作確認したところでは、11gR1のLinux版のクライアントで

Caused by: java.sql.SQLException: OCI : OCIDirPathPrepare failed. ORA-01009: missing mandatory parameter

        at org.embulk.output.oracle.oci.OCIWrapper.throwException(OCIWrapper.java:74)
        at org.embulk.output.oracle.oci.OCIWrapper.prepareLoad(OCIWrapper.java:40)
        at org.embulk.output.oracle.oci.OCIManager.open(OCIManager.java:34)

というエラーが発生した。

 

まとめると、こんな感じ。

モードバージョン
11gR111gR212c
normal ×
direct ×
oci Linuxは×

結構×があるな…。

 

まとめ

embulk-output-oracle(0.2.2)では、通常のINSERTの他に、ダイレクト・パス・インサート、OCIによるダイレクト・パス・インサートもできるようになった。

OCIはちょっと環境構築が面倒だけどこの中で最速で、embulk-input-filesplitを組み合わせると更に高速化するのでお勧め。

どのくらい速くなるかは、近いうちに改めてまとめる予定。

 

あと、embulk-output-jdbcのcommitterになりました。

 

Embulk 0.5.4
embulk-output-oracle 0.2.2

 

2015/4/17: ymlファイル中のcolumnsのnameを小文字から大文字に修正しました。Oracle上のカラム名はおそらく大文字の場合が多いと思いますが、大文字/小文字も含めてカラム名が一致しないと無視されてしまいます。