『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』のアーカイブをダウンロードし、解凍する。
# cd /usr/lib # unzip oraloader-1.1.0.0.1.x86_64.zip # ls -d ora* oraloader-1.1.0.0.1
# vi /etc/bashrc
export OLH_HOME=/usr/lib/oraloader-1.1.0.0.1
参考: Getting Started with Oracle Big Data Connectors
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]
<?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ジョブ用のコンフィグファイルを作成する。
<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>
/user/hishidama/emp2-map.xml
)で、ロードするデータを用意する。
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するのと基本的には変わらないと思われる。
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と同様。
テーブル名だけ今回のテーブル名に変更する。
<?xml version="1.0" encoding="UTF-8"?> <LOADER_MAP> <SCHEMA>SCOTT</SCHEMA> <TABLE>EMP2P</TABLE> 〜 </LOADER_MAP>
Oracle LoaderのHadoopジョブ用のコンフィグファイルは以下の様になる。
<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のライブラリーの場所を指定する必要がある。
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にセットされるよう修正する必要がある。
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以降が必要)のがいけないんだろうか。
CSVファイルを出力する例。[2012-06-27]
参考: Delimited Text Output
このCSVファイルはHDFS上に生成されるので、DBサーバーのローカルに転送し、SQL*Loaderでテーブルにロードする。
SQL*Loader用の制御ファイルも一緒に生成される。
DBに直接接続できない環境で、一旦ファイルに落としてロードする想定らしい。
コンフィグファイルのOutputFormatだけ、DelimitedTextOutputFormatを指定する。
それ以外のloaderMap等はJDBCOutputFormatと全く同じ。
<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]
「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と全く同じ。
<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の為の設定を行う必要がある。(次回以降は不要)
$ 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のLOCATIONで指定されているファイルの中身が更新されるだけで、自動的にテーブルの内容も書き換わる。
したがって、Oracle Loader for
Hadoopを再実行し、HDFS上のデータファイル(同名であれば)をローカルに持ってくる(上書きする)だけで中間テーブルは新しい状態になる。
そこで、目的のテーブルへのSELECT-INSERTだけ再実行すればよい。
ただし、CREATE文のLOCATIONに書かれているファイル名はデータ量が多くなると増える(データ量に応じて増減する)ので、毎回CREATEし直す方が安全。[2012-07-04]
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