S-JIS[2012-06-26/2012-07-14] 変更履歴

Oracle Loader for Hadoop

Oracle Loader for Hadoop』について。


概要

『Oracle Loader for Hadoop(OLH)』は、HDFSからOracle(RDB)へロードするライブラリーおよびツール。
(使用要件(対象バージョン)→Required Software

MapReduceで動作するので、Mapタスクで分散して処理される。

読み込めるHDFS上のファイルは、デフォルトではCSVやTSVファイル。
自分でInputFormatを自作することも出来る。サンプルとしてAvroファイルの読み込みクラスがある。

テーブルへのロード方法は4種類ある。


インストール

『Oracle Loader for Hadoop』のアーカイブをダウンロードし、解凍する。

  1. アーカイブファイルをダウンロードする。
    1. Oracleのホームページの上部の「ダウンロード」→「Databases」をクリックし、ソフトウェア・ダウンロードページを開く。
    2. Databaseカテゴリーの「Big Data Connectors」をクリックし、Big Data Connectors Downloadsページを開く。
    3. 「Accept License Agreement」を選択し、「Oracle Loader for Hadoop」をクリックする。
    4. ここでOracleサイトにサインインするとアーカイブ(oraloader-1.1.0.0.1.x86_64.zip)がダウンロードできる。
  2. アーカイブファイルを適当な場所に解凍する。
    # cd /usr/lib
    # unzip oraloader-1.1.0.0.1.x86_64.zip
    # ls -d ora*
    oraloader-1.1.0.0.1
  3. 環境変数を定義しておく。
    # vi /etc/bashrc
    export OLH_HOME=/usr/lib/oraloader-1.1.0.0.1

参考: Getting Started with Oracle Big Data Connectors


実行例(JDBCOutputFormat)

JDBCでインサートする方式の例。
参考: JDBC Output

まず、ロード先テーブルを用意する。
今回はEMPと同レイアウトのEMP2テーブルを作成し、そこにロードしてみる。

$ sqlplus scott

SQL> create table emp2 as select * from emp where 1=0;

Table created.

SQL> desc emp2
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 EMPNO                                              NUMBER(4)
 ENAME                                              VARCHAR2(10)
 JOB                                                VARCHAR2(9)
 MGR                                                NUMBER(4)
 HIREDATE                                           DATE
 SAL                                                NUMBER(7,2)
 COMM                                               NUMBER(7,2)
 DEPTNO                                             NUMBER(2)

SQL> select * from emp2;

no rows selected

次に、loaderMapドキュメント(ロード先のテーブル定義を記述したXMLファイル)を作成する。

このloaderMapドキュメントは、(スタンドアローンモードの場合はシェルと同じ場所にあればいいが、)HDFS上に配置する必要がある。[2012-07-02]

emp2-map.xml

<?xml version="1.0" encoding="UTF-8"?>
<LOADER_MAP>
<SCHEMA>SCOTT</SCHEMA>
<TABLE>EMP2</TABLE>
<COLUMN field="empNo">EMPNO</COLUMN>
<COLUMN field="ename">ENAME</COLUMN>
<COLUMN field="job">JOB</COLUMN>
<COLUMN field="mgr">MGR</COLUMN>
<COLUMN field="hireDate" format="yyyy/MM/dd HH:mm:ss">HIREDATE</COLUMN>
<COLUMN field="sal">SAL</COLUMN>
<COLUMN field="comm">COMM</COLUMN>
<COLUMN field="deptNo">DEPTNO</COLUMN>
</LOADER_MAP>

COLUMNタグのfield属性で、各カラムのCSVファイル上のカラム名を付ける。
COLUMNタグのボディー部でテーブル上のカラム名を指定する。

DATE型のカラムに対しては、JavaのDateFormat形式の書式を指定する。
(TIMESTAMP型のカラムは書式を指定できず、デフォルト形式で固定されているっぽい)


そして、Oracle LoaderのHadoopジョブ用のコンフィグファイルを作成する。

emp2-conf.xml

<configuration>
  <property>
    <name>mapreduce.inputformat.class</name>
    <value>oracle.hadoop.loader.lib.input.DelimitedTextInputFormat</value>
    <description> Name of the class implementing InputFormat </description>
  </property>

  <property>
    <name>oracle.hadoop.loader.input.fieldNames</name>
    <value>empNo,ename,job,mgr,hireDate,sal,comm,deptNo</value>
  </property>

  <property>
    <name>mapreduce.outputformat.class</name>
    <value>oracle.hadoop.loader.lib.output.JDBCOutputFormat</value>
    <description> Output mode after the loader job executes on Hadoop </description>
  </property>

  <property>
    <name>oracle.hadoop.loader.loaderMapFile</name>
    <value>emp2-map.xml</value>
    <description> The loaderMap file specifying the mapping of input data fields to the table columns </description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.user</name>
    <value>scott</value>
    <description> Name of the user connecting to the database</description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.password</name>
    <value>tiger</value>
    <description>Password of the user connecting to the database</description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.url</name>
    <value>jdbc:oracle:thin:@localhost:1521:orcl</value>
    <description> Database connection string </description>
  </property>
</configuration>

で、ロードするデータを用意する。

emp.csv

7369,SMITH,CLERK,7902,1980/12/17 00:00:00,800,,20
7499,ALLEN,SALESMAN,7698,1981/02/20 00:00:00,1600,300,30
7521,WARD,SALESMAN,7698,1981/02/22 00:00:00,1250,500,30
7566,JONES,MANAGER,7839,1981/04/02 00:00:00,2975,,20
7654,MARTIN,SALESMAN,7698,1981/09/28 00:00:00,1250,1400,30
7698,BLAKE,MANAGER,7839,1981/05/01 00:00:00,2850,,30
7782,CLARK,MANAGER,7839,1981/06/09 00:00:00,2450,,10
7788,SCOTT,ANALYST,7566,1987/04/19 00:00:00,3000,,20
7839,KING,PRESIDENT,,1981/11/17 00:00:00,5000,,10
7844,TURNER,SALESMAN,7698,1981/09/08 00:00:00,1500,0,30
7876,ADAMS,CLERK,7788,1987/05/23 00:00:00,1100,,20
7900,JAMES,CLERK,7698,1981/12/03 00:00:00,950,,30
7902,FORD,ANALYST,7566,1981/12/03 00:00:00,3000,,20
7934,MILLER,CLERK,7782,1982/01/23 00:00:00,1300,,10

これでようやく実行できる。

$ pwd
/home/hishidama/oraloader

$ ls
emp.csv  emp2-conf.xml  emp2-map.xml
$ export HADOOP_CLASSPATH="$OLH_HOME/jlib/*"

$ hadoop jar $OLH_HOME/jlib/oraloader.jar oracle.hadoop.loader.OraLoader \
  -conf emp2-conf.xml \
  -D mapred.input.dir=file:///home/hishidama/oraloader/emp.csv \
  -D mapred.output.dir=emp2log

今回はスタンドアローンモードで試したので、emp.csvファイルはローカル(file://)に置いているが、実際はHDFSのパスになるだろう。

出力先(ロード先)はRDB上のテーブルなのに、mapred.output.dirを指定する必要がある。
ここには、(MapReduceの出力先なのに)ログファイルが出力される^^;

$ ls
emp.csv  emp2log  emp2-conf.xml  emp2-map.xml  olhcache

$ ls emp2log
_SUCCESS  oraloader-00000-jdbc.log  oraloader-report.txt

コンフィグファイルでJDBCOutputFormatを指定した場合はJDBC(PreparedStatementのaddBatch・executeBatch)によるINSERT文になるようなので、
Sqoopのバッチモードでexportするのと基本的には変わらないと思われる。


実行例(OCIOutputFormat)

OCI(Oracle Call Interface)によるダイレクトパスでテーブルにロードする例。[2012-06-27]
参考: Oracle OCI Direct Path Output

ただし、以下のような制限がある。


ロード対象テーブルは以下のような感じになる。

create table emp2p
(
 EMPNO      NUMBER(4),
 ENAME      VARCHAR2(10),
 JOB        VARCHAR2(9),
 MGR        NUMBER(4),
 HIREDATE   DATE,
 SAL        NUMBER(7,2),
 COMM       NUMBER(7,2),
 DEPTNO     NUMBER(2)
)
partition by hash(EMPNO)
partitions 3
;

loaderMapドキュメント(ロード先のテーブル定義を記述したXMLファイル)はJDBCOutputFormatと同様

テーブル名だけ今回のテーブル名に変更する。

emp2-map.xml

<?xml version="1.0" encoding="UTF-8"?>
<LOADER_MAP>
<SCHEMA>SCOTT</SCHEMA>
<TABLE>EMP2P</TABLE>
〜
</LOADER_MAP>

Oracle LoaderのHadoopジョブ用のコンフィグファイルは以下の様になる。

emp2-conf.xml

<configuration>
  <property>
    <name>mapreduce.inputformat.class</name>
    <value>oracle.hadoop.loader.lib.input.DelimitedTextInputFormat</value>
    <description> Name of the class implementing InputFormat </description>
  </property>

  <property>
    <name>oracle.hadoop.loader.input.fieldNames</name>
    <value>empNo,ename,job,mgr,hireDate,sal,comm,deptNo</value>
  </property>

  <property>
    <name>mapreduce.outputformat.class</name>
    <value>oracle.hadoop.loader.lib.output.OCIOutputFormat</value>
    <description> Output mode after the loader job executes on Hadoop </description>
  </property>
  <property>
    <name>mapred.reduce.tasks</name>
    <value>3</value>
  </property>

  <property>
    <name>oracle.hadoop.loader.loaderMapFile</name>
    <value>emp2-map.xml</value>
    <description> The loaderMap file specifying the mapping of input data fields to the table columns </description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.user</name>
    <value>scott</value>
    <description> Name of the user connecting to the database</description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.password</name>
    <value>tiger</value>
    <description>Password of the user connecting to the database</description>
  </property>

  <property>
    <name>oracle.hadoop.loader.connection.url</name>
    <value>jdbc:oracle:oci:@orcl</value>
    <description> Database connection string </description>
  </property>
</configuration>

InputFormatはJDBCと同じ。

OutputFormatにOCIOutputFormatを指定する。
併せて、Reducerタスク数も指定してみた。

接続URLは、(thin形式でなく)oci形式で指定する。


実行する際は、OCIのライブラリーの場所を指定する必要がある。

run.sh:

export HADOOP_CLASSPATH="$OLH_HOME/jlib/*"
export JAVA_LIBRARY_PATH=$OLH_HOME/lib:$ORACLE_HOME/lib
export LD_LIBRARY_PATH=$JAVA_LIBRARY_PATH

rm -rf emp2log

hadoop jar $OLH_HOME/jlib/oraloader.jar oracle.hadoop.loader.OraLoader \
-conf emp2-conf.xml \
-D mapred.input.dir=file:///home/hishidama/oraloader/emp.csv \
-D mapred.output.dir=emp2log

CDH3のhadoopシェルでは、環境変数JAVA_LIBRARY_PATHを設定しておくとjavaコマンドのjava.library.pathにセットしてくれるらしい。
CDH3版以外のHadoopを使う場合は、java.library.pathにセットされるよう修正する必要がある。


OCIOutputFormatでのエラー

12/06/27 22:43:11 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Exception in thread "main" java.lang.UnsatisfiedLinkError: oracle.jdbc.driver.T2CConnection.getLibraryVersionNumber()I
        at oracle.jdbc.driver.T2CConnection.getLibraryVersionNumber(Native Method)
        at oracle.jdbc.driver.T2CConnection$1.run(T2CConnection.java:3552)
        at java.security.AccessController.doPrivileged(Native Method)
〜

JDBCのバージョン違いによるエラー。
$OLH_HOME/jlib/ojdbc6.jarと、$ORACLE_HOME/jdbc/lib/ojdbc6.jarの細かいバージョンが異なっている場合、このようなエラーになるっぽい。

したがって、$ORACLE_HOMEのojdbc6.jarでOracle Loaderのjarファイルを置き換えてやればよい。

# cp -p $ORACLE_HOME/jdbc/lib/ojdbc6.jar $OLH_HOME/jlib/

12/06/27 21:47:17 ERROR security.UserGroupInformation: PriviledgedActionException as:hishidama (auth:SIMPLE) cause:java.io.IOException: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-12545: Connect failed because target host or object does not exist

Exception in thread "main" java.io.IOException: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-12545: Connect failed because target host or object does not exist
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.checkOutputSpecs(OCIOutputFormat.java:473)
        at org.apache.hadoop.mapred.JobClient$2.run(JobClient.java:872)
        at org.apache.hadoop.mapred.JobClient$2.run(JobClient.java:833)
        at java.security.AccessController.doPrivileged(Native Method)
〜
Caused by: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-12545: Connect failed because target host or object does not exist

        at oracle.hadoop.loader.lib.output.OCIOutputFormat.checkError(OCIOutputFormat.java:874)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.logon(OCIOutputFormat.java:605)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.logon(OCIOutputFormat.java:555)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.checkOutputSpecs(OCIOutputFormat.java:469)
        ... 16 more

ホストやオブジェクト(ORACLE_SIDのことか?)が見つからないというエラー。

接続URLをthin形式にしているとこのようなエラーになる。
したがって、接続URLをoci形式にする。

  <property>
    <name>oracle.hadoop.loader.connection.url</name>
<!--<value>jdbc:oracle:thin:@localhost:1521:orcl</value>-->
    <value>jdbc:oracle:oci:@orcl</value>
    <description> Database connection string </description>
  </property>

12/06/28 22:31:36 ERROR metadata.OraLoaderMetadata: missing/invalid property: oracle.hadoop.loader.tableMetadataFile
Exception in thread "main" oracle.hadoop.loader.OraLoaderException: Oracle database connection error: ORA-12154: TNS:could not resolve the connect identifier specified

        at oracle.hadoop.loader.metadata.OraLoaderMetadata$1errhdlr.handleEx(OraLoaderMetadata.java:523)
        at oracle.hadoop.loader.metadata.OraLoaderMetadata.(OraLoaderMetadata.java:554)
        at oracle.hadoop.loader.OraLoader.run(OraLoader.java:372)
        at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:65)
        at oracle.hadoop.loader.OraLoader.main(OraLoader.java:734)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.hadoop.util.RunJar.main(RunJar.java:197)
Caused by: java.sql.SQLException: ORA-12154: TNS:could not resolve the connect identifier specified

        at oracle.jdbc.driver.T2CConnection.checkError(T2CConnection.java:737)
        at oracle.jdbc.driver.T2CConnection.logon(T2CConnection.java:401)
        at oracle.jdbc.driver.PhysicalConnection.(PhysicalConnection.java:531)
        at oracle.jdbc.driver.T2CConnection.(T2CConnection.java:148)
        at oracle.jdbc.driver.T2CDriverExtension.getConnection(T2CDriverExtension.java:53)
        at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:503)
        at oracle.jdbc.pool.OracleDataSource.getPhysicalConnection(OracleDataSource.java:280)
        at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:207)
        at oracle.jdbc.pool.OracleDataSource.getConnection(OracleDataSource.java:157)
        at oracle.hadoop.loader.Utils.getConnection(Utils.java:263)
        at oracle.hadoop.loader.metadata.OraLoaderMetadata.(OraLoaderMetadata.java:547)
        ... 8 more

OCIによる接続(jdbc:oracle:oci:@orcl)が上手く行かない。[2012-06-28]

どちらかと言えばOracle環境の問題。
自分の場合は、hadoopシェルを実行しているUNIXユーザーに、$ORACLE_HOME/network/admin/tnsnames.oraの参照権限が無くてこのエラーになっていた。


Reduceタスクで以下のような例外が発生して全滅した場合。[2012-07-02]

java.io.IOException: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-12154: TNS:could not resolve the connect identifier specified

	at oracle.hadoop.loader.lib.output.OCIOutputFormat.getRecordWriter(OCIOutputFormat.java:363)
	at org.apache.hadoop.mapred.ReduceTask.runNewReducer(ReduceTask.java:559)
	at org.apache.hadoop.mapred.ReduceTask.run(ReduceTask.java:414)
	at org.apache.hadoop.mapred.Child$4.run(Child.java:270)
	at java.security.AccessController.doPrivileged(Native Method)
	at javax.security.auth.Subject.doAs(Subject.java:396)
	at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1127)
	at org.apache.hadoop.mapred.Child.main(Child.java:264)
Caused by: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-12154: TNS:could not resolve the connect identifier specified

	at oracle.hadoop.loader.lib.output.OCIOutputFormat.checkError(OCIOutputFormat.java:874)
	at oracle.hadoop.loader.lib.output.OCIOutputFormat.logon(OCIOutputFormat.java:605)
	at

スレーブノードでOCIによる接続(jdbc:oracle:oci:@orcl)が上手く行っていない。
OCI接続では各スレーブノードにもtnsnames.oraが必要であり、存在しないとこの例外が出る。

Oracleクライアントをインストールする必要までは無いが、tnsnames.oraだけは全てのスレーブノードに配置しなければならない。
そして、conf.xmlにtnsnames.oraのディレクトリーを指定する必要がある。

  <property>
    <name>oracle.hadoop.loader.connection.tns_admin</name>
    <value>/opt/oracle/app/product/11.2.0/dbhome_1/network/admin</value>
  </property>

java.io.IOException: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-26017: global indexes not allowed for direct path load of table partition SCOTT.EMP2P

        at oracle.hadoop.loader.lib.output.DBOutputFormat$DBRecordWriter.write(DBOutputFormat.java:531)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat$OCIRecordWriter.write(OCIOutputFormat.java:906)
        at oracle.hadoop.loader.lib.output.DBOutputFormat$DBRecordWriter.write(DBOutputFormat.java:358)
        at org.apache.hadoop.mapred.ReduceTask$NewTrackingRecordWriter.write(ReduceTask.java:514)
        at org.apache.hadoop.mapreduce.TaskInputOutputContext.write(TaskInputOutputContext.java:80)
        at oracle.hadoop.loader.OraLoaderReducer.reduce(OraLoaderReducer.java:123)
        at oracle.hadoop.loader.OraLoaderReducer.reduce(OraLoaderReducer.java:59)
        at org.apache.hadoop.mapreduce.Reducer.run(Reducer.java:176)
        at org.apache.hadoop.mapred.ReduceTask.runNewReducer(ReduceTask.java:572)
        at org.apache.hadoop.mapred.ReduceTask.run(ReduceTask.java:414)
        at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:256)
Caused by: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-26017: global indexes not allowed for direct path load of table partition SCOTT.EMP2P

        at oracle.hadoop.loader.lib.output.OCIOutputFormat.checkError(OCIOutputFormat.java:874)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.prepareDirectPath(OCIOutputFormat.java:773)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat.access$600(OCIOutputFormat.java:103)
        at oracle.hadoop.loader.lib.output.OCIOutputFormat$OCIRecordWriter.openBindContext(OCIOutputFormat.java:1022)
        at oracle.hadoop.loader.lib.output.DBOutputFormat$DBRecordWriter.write(DBOutputFormat.java:522)
        ... 10 more

パーティションテーブルにグローバルインデックスがあるので、ダイレクト・パス・ロードが出来ない、というエラー。
グローバル索引を削除するしかないかなぁ。

ローカルインデックスも駄目っぽい。以下のような例外になる。[2012-07-03]

java.io.IOException: oracle.hadoop.loader.OraLoaderException: an OCI error occurred: ORA-26002: Table SCOTT.EMP2P has index defined upon it.

Exception in thread "main" java.lang.NullPointerException
        at java.nio.ByteBuffer.wrap(ByteBuffer.java:373)
        at oracle.hadoop.loader.DBPartition$VarLenDecoderIterator.(DBPartition.java:809)
        at oracle.hadoop.loader.DBPartition.decodeVariableLengthEncodedList(DBPartition.java:791)
        at oracle.hadoop.loader.DBKey.parseHiboundVal(DBKey.java:114)
        at oracle.hadoop.loader.DBStaticRangePartition.(DBStaticRangePartition.java:88)
        at oracle.hadoop.loader.DBPartition.getStrategy(DBPartition.java:381)
        at oracle.hadoop.loader.DBPartition.(DBPartition.java:242)
        at oracle.hadoop.loader.OraLoader.checkRuntime(OraLoader.java:190)
        at oracle.hadoop.loader.OraLoader.run(OraLoader.java:375)
        at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:65)
        at oracle.hadoop.loader.OraLoader.main(OraLoader.java:734)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.hadoop.util.RunJar.main(RunJar.java:197)

パーティションテーブルハッシュパーティション以外(レンジやリスト)だとこの例外が発生する。[2012-06-28]
ドキュメントによると、その辺りのパーティションは大丈夫なはずなんだが。
試しているOracleが11.2.0.1.0で微妙に古い(11.2.0.2以降が必要)のがいけないんだろうか。


実行例(DelimitedTextOutputFormat)

CSVファイルを出力する例。[2012-06-27]
参考: Delimited Text Output

このCSVファイルはHDFS上に生成されるので、DBサーバーのローカルに転送し、SQL*Loaderでテーブルにロードする。
SQL*Loader用の制御ファイルも一緒に生成される。

DBに直接接続できない環境で、一旦ファイルに落としてロードする想定らしい。


コンフィグファイルのOutputFormatだけ、DelimitedTextOutputFormatを指定する。
それ以外のloaderMap等はJDBCOutputFormatと全く同じ。

emp2-conf.xml:

  <property>
    <name>mapreduce.outputformat.class</name>
    <value>oracle.hadoop.loader.lib.output.DelimitedTextOutputFormat</value>
    <description> Output mode after the loader job executes on Hadoop </description>
  </property>

実行方法もJDBCOutputFormatと全く同じ。

$ export HADOOP_CLASSPATH="$OLH_HOME/jlib/*"

$ hadoop jar $OLH_HOME/jlib/oraloader.jar oracle.hadoop.loader.OraLoader \
  -conf emp2-conf.xml \
  -D mapred.input.dir=file:///home/hishidama/oraloader/emp.csv \
  -D mapred.output.dir=emp2log

今回試した環境はスタンドアローンHadoopなので、入力ファイルも出力先もローカルとなっている。
実際の分散環境なら、入力ファイルはHDFS上のファイルで、出力先もHDFS上になるだろう。

$ ls emp2log
_SUCCESS  oraloader-00000-csv-0.dat  oraloader-csv.ctl  oraloader-csv.sql  oraloader-report.txt
ファイル名 内容
oraloader-00000-csv-0.dat データ本体。CSVファイル。
タスク数が多い場合は、たぶん数値違いのファイルがいくつか作られる。
oraloader-csv.ctl SQL*Loaderのコントロールファイル
oraloader-csv.sql データを直接対象テーブルへロードする以外に、
外部テーブルを定義し、そこからSELECT-INSERTで対象テーブルにコピーする手段が考えられる。
このSQLファイルは、その手順と外部テーブルのCREATE文が書かれている。
(SQL*Loaderを使ってロードするより高速)
DataPumpOutputFormatでロードするのと同じ方法で出来る。

生成されたコントロールファイルを使ってテーブルにロードする方法。

$ sqlldr scott/tiger control=emp2log/oraloader-csv.ctl data=emp2log/oraloader-00000-csv-0.dat

ただし、データ量が増えるとdatファイル(CSVファイル)の個数が増える(リトライしたタスクがあった場合はファイル名の数値も変わるだろう)から、ディレクトリー内にあるファイル全てを(ファイル名を毎回確認して)ロードする必要がある。[2012-07-04]


実行例(DataPumpOutputFormat)

「Data Pump」形式のバイナリーファイルを出力する例。[2012-06-27]
参考: Oracle Data Pump Output

DelimitedTextOutputFormatと同様、DBに直接接続できない環境で、一旦ファイルに落としてロードする想定だと思われる。

「Data Pump」は、「Data Pump Export/Importユーティリティー」で扱われるバイナリー形式らしい。
たぶんimpdpというコマンドでテーブルにロードすることが出来ると思う。
が、一旦別テーブルにロードし、SELECT-INSERTで対象テーブルにコピーすることも出来る。(その為の手順と別テーブルのCREATE文も生成される)

なお、Data Pumpのパッチを当てないといけないらしい。(→Required Software
自分の環境では、パッチを当てなくてもとりあえず動いたけど。


コンフィグファイルのOutputFormatだけ、DataPumpOutputFormatを指定する。
それ以外のloaderMap等はJDBCOutputFormatと全く同じ。

emp2-conf.xml

  <property>
    <name>mapreduce.outputformat.class</name>
    <value>oracle.hadoop.loader.lib.output.DataPumpOutputFormat</value>
    <description> Output mode after the loader job executes on Hadoop </description>
  </property>

実行方法もJDBCOutputFormatと全く同じ。

$ export HADOOP_CLASSPATH="$OLH_HOME/jlib/*"

$ hadoop jar $OLH_HOME/jlib/oraloader.jar oracle.hadoop.loader.OraLoader \
  -conf emp2-conf.xml \
  -D mapred.input.dir=file:///home/hishidama/oraloader/emp.csv \
  -D mapred.output.dir=emp2log

今回試した環境はスタンドアローンHadoopなので、入力ファイルも出力先もローカルとなっている。
実際の分散環境なら、入力ファイルはHDFS上のファイルで、出力先もHDFS上になるだろう。

$ ls emp2log
_SUCCESS  oraloader-00000-dp-0.dat  oraloader-dp.sql  oraloader-report.txt
ファイル名 内容
oraloader-00000-dp-0.dat データ本体。バイナリーファイル。
タスク数が多い場合は、たぶん数値違いのファイルがいくつか作られる。
oraloader-dp.sql このSQLファイルは、外部テーブルのCREATE文と、それを使って対象テーブルにロードする手順が書かれている。

oraloader-dp.sqlの中身は、以下の様になっている。

--Oracle Loader for Hadoop Release 1.1.0.0.1 - Production
--
--Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
--
--Generated by DataPumpOutputFormat
--
--CREATE OR REPLACE DIRECTORY OLH_EXTTAB_DIR AS '...';
--GRANT READ, WRITE ON DIRECTORY OLH_EXTTAB_DIR TO SCOTT;
--
--ALTER SESSION ENABLE PARALLEL DML;
--INSERT INTO "SCOTT"."EMP2" SELECT * FROM "SCOTT"."EXT_EMP2";
--
CREATE TABLE  "SCOTT"."EXT_EMP2"
(
   "EMPNO"                          NUMBER(4,0),
   "ENAME"                          VARCHAR2(10),
   "JOB"                            VARCHAR2(9),
   "MGR"                            NUMBER(4,0),
   "HIREDATE"                       DATE,
   "SAL"                            NUMBER(7,2),
   "COMM"                           NUMBER(7,2),
   "DEPTNO"                         NUMBER(2,0)
)
ORGANIZATION EXTERNAL
  (TYPE ORACLE_DATAPUMP
   DEFAULT DIRECTORY OLH_EXTTAB_DIR
   LOCATION ('oraloader-00000-dp-0.dat')
  );

コメントで書かれているのがロード手順、SQL本体が中間テーブルのCREATE文。

external table外部テーブル)という仕組みのテーブルで中間テーブルを作成し、そこから対象テーブルへコピーする。
external tableはデータファイルの場所を直接指定できるので、そこへのロードという処理は必要ない。SQL*Loaderを使うより速 い!

このやり方の場合、OLH_EXTTAB_DIRという名前でディレクトリーを指定する。
この名前はHadoop用コンフィグファイルで指定することも出来る。
(sqlファイルの中身は、コンフィグファイルで指定したディレクトリー名にちゃんと変わる)

  <property>
    <name>oracle.hadoop.loader.extTabDirectoryName</name>
    <value>OLH_EXTTAB_DIR</value>
    <description>
     The name of the Oracle directory object for the external table's
     LOCATION data files. This property applies only to the CSV and 
     DataPump output formats.
    </description>
  </property>

external tableを使ったロード(初回)

最初に、external tableの為の設定を行う必要がある。(次回以降は不要)

$ sqlplus sys as sysdba

SQL> CREATE OR REPLACE DIRECTORY OLH_EXTTAB_DIR AS '/home/hishidama/oraloader/emp2log';

SQL> GRANT READ, WRITE ON DIRECTORY OLH_EXTTAB_DIR TO SCOTT;

ここで実行すべきSQLは、oraloader-dp.sqlのコメントに書かれている。

「CREATE DIRECTORY」で、ディレクトリー指定を作成する。
名前はOLH_EXTTAB_DIRで、具体的にはデータファイルのある場所を指定する。


そして、中間テーブルを作成する。(oraloader-dp.sqlのSQL本体を実行する)

$ sqlplus scott

SQL> CREATE TABLE  "SCOTT"."EXT_EMP2"
(
   "EMPNO"                          NUMBER(4,0),
   "ENAME"                          VARCHAR2(10),
   "JOB"                            VARCHAR2(9),
   "MGR"                            NUMBER(4,0),
   "HIREDATE"                       DATE,
   "SAL"                            NUMBER(7,2),
   "COMM"                           NUMBER(7,2),
   "DEPTNO"                         NUMBER(2,0)
)
ORGANIZATION EXTERNAL
  (TYPE ORACLE_DATAPUMP
   DEFAULT DIRECTORY OLH_EXTTAB_DIR
   LOCATION ('oraloader-00000-dp-0.dat')
  );

このCREATE文を実行するだけで、データも入っている状態になる。

SQL> select count(*) from ext_emp2;

  COUNT(*)
----------
        14

あとは、目的のテーブルへSELECT-INSERTしてやればよい。

SQL> ALTER SESSION ENABLE PARALLEL DML;

SQL> INSERT INTO "SCOTT"."EMP2" SELECT * FROM "SCOTT"."EXT_EMP2";

external tableを使ったロード(2回目以降)

external tableのLOCATIONで指定されているファイルの中身が更新されるだけで、自動的にテーブルの内容も書き換わる。

したがって、Oracle Loader for Hadoopを再実行し、HDFS上のデータファイル(同名であれば)をローカルに持ってくる(上書きする)だけで中間テーブルは新しい状態になる。
そこで、目的のテーブルへのSELECT-INSERTだけ再実行すればよい。

ただし、CREATE文のLOCATIONに書かれているファイル名はデータ量が多くなると増える(データ量に応じて増減する)ので、毎回CREATEし直す方が安全。[2012-07-04]


external tableのエラー

SQL> select count(*) from ext_emp2
select count(*) from ext_emp2
*
ERROR at line 1:
ORA-29913: error in executing ODCIEXTTABLEOPEN callout
ORA-29400: data cartridge error
error opening file /home/hishidama/oraloader/emp2log/EXT_EMP2_6396.log

external tableへアクセスする際、その場所にログファイルを作成するらしい。
したがって、そのディレクトリーに書き込み権限が無いと、このようなエラーになる。

Oracle(RDBMS)はたぶんoracleユーザーで動いているので、oracleユーザーでそのディレクトリーへ移動でき、かつファイルを書き込める必要がありそう。


メタデータファイルの作成

Oracle Loaderで使用するメタデータファイルだけを生成することが出来る。[2012-07-14]

java -cp "$OLH_HOME/jlib/*" oracle.hadoop.loader.metadata.OraLoaderMetadata \
-user hishidama \
-connection_url jdbc:oracle:thin:@localhost:1521:orcl \
-table emp2 \
-output emp2-meta.xml

Hadoop目次へ戻る / Oracleへ戻る / 技術メモへ戻る
メールの送信先:ひしだま