S-JIS[2012-05-03/2012-06-01] 変更履歴

Sqoop MySQLサンプル

SqoopMySQLを扱う例。


準備

ここでは、SqoopでMySQLにアクセスする。
HadoopとSqoopとMySQLを同一マシン(CentOS)上で稼動させる。
Hadoopはスタンドアローン環境。

MySQLのJDBCドライバーSqoopのlibに配置しておく必要がある。

MySQLでサンプル用のDBとユーザーとテーブルを作っておく。

$ mysql -u root

mysql> create database test1 character set utf8;
mysql> show databases;

mysql> create user hishidama identified by 'hishidamal';	←パスワード
mysql> grant all privileges on test1.* to hishidama@localhost identified by 'hishidamac';	←接続用パスワード
mysql> flush privileges;

mysql> exit;
$ mysql -u hishidama -phishidamac test1

mysql> create table table1 (
         key1   char(4),
         data1  varchar(10),
         num1   int,
         primary key(key1)
       );
mysql> show tables;
mysql> desc table1;

mysql> insert into table1 values('A001', 'test', 123);
       insert into table1 values('A002', 'aa,bb\"cc', 456);
       insert into table1 values('A003', '', null);
       insert into table1 values('A004', null, null);
mysql> select * from table1;
mysql> commit;

テストデータの中にカンマとダブルクォーテーションおよびnullを含めてみた。


import

インポート(DB→HDFS)を実行してみる。

まずはシンプルに実行

$ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1

$ ls
table1  table1.java

$ ls table1
_SUCCESS  part-m-00000

$ cat table1/part-m-00000
A001,test,123
A002,aa,bb"cc,456
A003,,null
A004,null,null

テーブル名のディレクトリーと、テーブル名のJavaファイルが出来ている。
(→Javaソースを生成させない方法

ディレクトリー内にテーブルから取得されたデータが入っている。
が、データ文字列内のカンマがそのまま出力されているので、これを読み込んでも正しく反映できない。


CSVの形式を指定して実行

Sqoopでimportする際には区切り文字等を指定することが出来る。

$ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
  --fields-terminated-by ',' --lines-terminated-by '\n' --escaped-by '\\' --enclosed-by '\"' \
  --target-dir table1csv

$ ls
table1  table1.java  table1csv

$ cat table1csv/part-m-00000
"A001","test","123"
"A002","aa,bb\"cc","456"
"A003","","null"
"A004","null","null"

ついでに--target-dirを指定しているので、その名前のディレクトリーが作られている(指定しないとテーブル名のディレクトリーになる)。

パラメーター 説明 指定例 出力例
--lines-terminated-by 行区切り文字。
デフォルトは改行なので、特に指定する必要は無いだろう。
'\n'  
--fields-terminated-by フィールド区切り文字。 ','
'\t'
 
--enclosed-by データを囲む文字。
全データがこの文字で囲まれる。
'\"' "A001","test","123"
--optionally-enclosed-by データを囲む文字。
フィールド区切り文字がデータに含まれている場合はこの文字で囲まれる。
'\"' A005,"aa,bb,cc",456
--escaped-by エスケープ文字。
データを囲む文字と同じ文字がデータに含まれている場合、この文字が前に付く。
'\\' A006,aa\"bb'cc,456
--null-string 文字列データがNULLのときに出力される文字列。デフォルトは「null」。
文字列でないデータには影響しない。
'NULL!!!' A004,NULL!!!,null

シーケンスファイルとして出力

テキストファイルだけでなく、HadoopのSequenceFileとして出力することも出来る。

$ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 -as-sequencefile

$ ls
table1  table1.java

$ od -tx1a table1/part-m-00000
0000000 53 45 51 06 21 6f 72 67 2e 61 70 61 63 68 65 2e
          S   E   Q ack   !   o   r   g   .   a   p   a   c   h   e   .
0000020 68 61 64 6f 6f 70 2e 69 6f 2e 4c 6f 6e 67 57 72
          h   a   d   o   o   p   .   i   o   .   L   o   n   g   W   r
0000040 69 74 61 62 6c 65 06 74 61 62 6c 65 31 00 00 00
          i   t   a   b   l   e ack   t   a   b   l   e   1 nul nul nul
〜

値のクラスはテーブル名と同じになっている。
データと一緒に生成されているJavaソースファイルがこのクラスのソース。

クラス名は--class-nameで指定できる。

$ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
  -as-sequencefile --class-name com.example.Table1Writable \
  --target-dir table1sf

$ ls
com  table1  table1.java  table1sf

$ tree com
com
`-- example
    `-- Table1Writable.java

$ od -tx1a table1sf/part-m-00000
0000000 53 45 51 06 21 6f 72 67 2e 61 70 61 63 68 65 2e
          S   E   Q ack   !   o   r   g   .   a   p   a   c   h   e   .
0000020 68 61 64 6f 6f 70 2e 69 6f 2e 4c 6f 6e 67 57 72
          h   a   d   o   o   p   .   i   o   .   L   o   n   g   W   r
0000040 69 74 61 62 6c 65 1a 63 6f 6d 2e 65 78 61 6d 70
          i   t   a   b   l   e sub   c   o   m   .   e   x   a   m   p
0000060 6c 65 2e 54 61 62 6c 65 31 57 72 69 74 61 62 6c
          l   e   .   T   a   b   l   e   1   W   r   i   t   a   b   l
0000100 65 00 00 00 00 00 00 06 bf e8 b5 9b c7 55 d4 08
          e nul nul nul nul nul nul ack   ?   h   5 esc   G   U   T  bs
〜

生成されるソースもパッケージのディレクトリー階層下に作られる。

Javaソースを生成させない方法


export

エクスポート(HDFS→DB)を実行してみる。
(export実行時も、importと全く同じJavaソースが生成される。→Javaソースを生成させない方法


CSVファイルのエクスポート

exportのCSVに関するオプションは、インポートのときのオプションの前に「input」を付けたもの。

$ cat dir1csv/data.txt 
A001,"test",123
A002,"aa,bb\"cc",456
A003,"",null
A004,null,null
A005,NULL!!!,null

$ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
  --input-fields-terminated-by ',' --input-lines-terminated-by '\n' --input-escaped-by '\\' --input-optionally-enclosed-by '\"' \
  --input-null-string 'NULL!!!' \
  --export-dir dir1csv
mysql> select * from table1;
+------+----------+------+
| key1 | data1    | num1 |
+------+----------+------+
| A001 | test     |  123 | 
| A002 | aa,bb"cc |  456 | 
| A003 |          | NULL | 
| A004 | null     | NULL | 
| A005 | NULL     | NULL | 
+------+----------+------+
5 rows in set (0.00 sec)

シーケンスファイルのエクスポート

SequenfeFileに出力するときは-as-sequencefileで明示的にSequenceFileであることを指定したが、エクスポートのときはファイルの種類を自動的に判別してくれるようだ。

$ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
  --export-dir dir1sf

更新モード

exportは基本的にINSERTだが、UPDATEを使うことも出来る。[2012-06-01]

$ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
  --input-fields-terminated-by ',' --input-lines-terminated-by '\n' --input-escaped-by '\\' --input-optionally-enclosed-by '\"' --input-null-string 'NULL!!!' \
  --update-key key1 \
  --export-dir dir1csv

--update-keyを指定すると、そのカラムをWHERE条件としたUPDATE文でテーブルが更新される。
(カンマ区切りで複数のカラムを指定可能)

UPDATE table1 SET data1='test', num1=123 WHERE key1='A001'
UPDATE table1 SET data1='aa,bb"cc', num1=456 WHERE key1='A002'
UPDATE table1 SET data1='', num1=null WHERE key1='A003'
UPDATE table1 SET data1=null, num1=null WHERE key1='A004'
UPDATE table1 SET data1='NULL!!!', num1=null WHERE key1='A005'
UPDATE table1 SET data1='かきく', num1=789 WHERE key1='A006'

対象テーブルにデータがあれば更新される。
対象テーブルにデータが無ければ(WHERE条件に合致するレコードが無いので)何も更新されない。

「--update-mode allowinsert」を指定すると、対象テーブルにデータが無いときはINSERTしてくれる。
なのだが、MySQLは(まだ)対応していないようだ。(デフォルトは「--update-mode updateonly」)

12/06/01 23:12:19 ERROR tool.ExportTool: Error during export: Mixed update/insert is not supported against the target database yet

ステージングテーブル

exportにはステージング機能がある。[2012-06-01]
ステージングテーブルを経由して目的のテーブルにデータを転送するもの。

  1. 目的のテーブルと同一レイアウトのステージングテーブルを事前に作っておく。
  2. sqoop exportコマンドでステージングテーブルを指定する。
    $ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac \
      --input-fields-terminated-by ',' --input-lines-terminated-by '\n' --input-escaped-by '\\' --input-optionally-enclosed-by '\"' \
      --table table1 \
      --staging-table table1_stage --clear-staging-table \
      --export-dir dir1csv

内部的な動作は以下の様になる。

  1. --clear-staging-tableを付けている場合、転送前にステージングテーブルのデータは全て削除される。
    DELETE FROM table1_stage
    commit
  2. ステージングテーブルに対してデータがINSERTされる。
    INSERT INTO table1_stage (key1, data1, num1) VALUES ('A001', 'test', 123), …
    …
    commit
  3. ステージングテーブルのデータを目的のテーブルへ移送する。そして、ステージングテーブルはクリアされる。この2つは同一トランザクションで行われる。
    INSERT INTO table1 ( SELECT * FROM table1_stage )
    DELETE FROM table1_stage
    commit

Sqoopは複数のノードから並列でインサートするので、コミットタイミングはばらばら。目的のテーブルに直接インサートすると、中途半端な状態が出来てしまう。
それが嫌な場合はステージングテーブルを使うと、目的のテーブルは一気に更新されることになる。


自動生成されるJavaソース

importexportを実行すると、(デフォルトでは)テーブル名と同名のJavaソースファイルが生成される。

テキストファイル(CSVファイル)ではこのクラスのパーサーメソッドが使われると思われる。
シーケンスファイルでは値(Writable)としてこのクラスが使われる。

importやexportの際には、このクラスを用いてデータのシリアライズ/デシリアライズを行うようだ。
このクラスの1インスタンスが1レコード分のデータを保持する。
このクラスはHadoopのWritableとJDBCアクセスの為のDBWritableというインターフェースを実装し、その他にテキスト(CSV等)の為のパーサーとtoString()メソッド等が入っている。

主なメソッド
メソッド 備考
get_フィールド()
set_フィールド(値)
with_フィールド(値)
フィールド(データ)のセッター・ゲッターメソッド。
(withは、自分自身のインスタンスを返すセッターメソッド)
equals()  
readFields(ResultSet) DBから読み込んだResultSetを元に自分自身のフィールドに値をセットする。
write(PreparedStatement) 自分自身の値をPreperedStatementにセットする。(DBへの書き込み用)
readFields(DataInput) HadoopのWritableの読込用メソッド。自分自身のフィールドに値をセットする。
write(DataOutput) HadoopのWritableの出力用メソッド。
toString() 文字列として出力する。(CSVとして整形する)
parse(CharSequence) 文字列を(CSVとして)パースし、自分自身のフィールドに値をセットする。
clone()  
getFieldMap() フィールド名と値のMapを返す。
setField(名前, 値) フィールド名を指定して値をセットする。

Writableのメソッドでは、値がnullかどうかに応じてbooleanの値も入出力している。
オプションでnullを扱うかどうか切り替えられそうな気はする。


codegen

importexportを実行する度にJavaソースが生成されるのは鬱陶しい。
codegenによって事前に一度だけ生成し、importやexportでそのクラスを使うことが出来る。

  1. codegenによってJavaソースを生成する。
    (CSVファイルを扱いたい場合は、import時のCSVの指定と同様のオプションを指定する)
    $ sqoop codegen --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
      --fields-terminated-by ',' --lines-terminated-by '\n' --escaped-by '\\' --optionally-enclosed-by '\"' \
      --class-name com.sample.Table1 \
      --outdir src
    $ ls
    src
    $ tree src
    src
    `-- com
        `-- sample
            `-- Table1.java
  2. コンパイルする。(CLASSPATHにHadoopとSqoopのjarファイルを含める必要がある)
    $ mkdir classes
    $ javac -cp classes:/usr/lib/hadoop/hadoop-core.jar:/usr/lib/sqoop/sqoop-1.3.0-cdh3u3.jar \
      -d classes -sourcepath src \
      src/com/sample/Table1.java 
    $ tree classes
    classes/
    `-- com
        `-- sample
            `-- Table1.class
  3. jarファイル化する。
    $ jar cf sqoop1.jar -C classes .
    $ jar tf sqoop1.jar	←中身確認
    META-INF/
    META-INF/MANIFEST.MF
    com/
    com/sample/
    com/sample/Table1.class
  4. importやexportではそのjarファイルを指定する。
    CSVファイルへのインポート
    $ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
      --jar-file sqoop1.jar --class-name com.sample.Table1 \
      --target-dir table1csv
    シーケンスファイルへのインポート
    $ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
      --jar-file sqoop1.jar --class-name com.sample.Table1 \
      --target-dir table1sf -as-sequencefile
    エクスポート
    $ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --table table1 \
      --jar-file sqoop1.jar --class-name com.sample.Table1 \
      --export-dir dir

importやexport時に--jar-fileを指定すると、Javaソースが自動生成されなくなる。
その代わりに(jarファイルの中に入っている)実行時に使用するクラスを--class-nameで指定する必要がある。


direct import/export

import/exportにはdirectというオプションがあり、RDBMS固有の機能を使って 汎用SQLよりも高速に処理できる(はず)。[2012-05-12]

$ sqoop import --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --direct --table table1 \
  --target-dir table1csv
$ sqoop export --connect jdbc:mysql://localhost/test1 --username hishidama --password hishidamac --direct --table table1 \
  --export-dir dir1csv

Sqoop1.3.0のMySQLのダイレクトモードは以下のような特徴・制約がある。


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