Pig on Tez on CDHでGPLライブラリを追加

こんにちは。

前回の記事（下記）に引き続き、Pig on Tez on CDHのお試しです。
http://qiita.com/kimutansk/items/8a91baf476fff4232634

## Pig on Tez on CDHでGPLライブラリを追加してみる理由
Pigを使う際、前提となるライブラリや設定の追加が必要になるケースがあるためです。

Pig UDFの場合はPig Script中で明示的にロードを行うため、
HDFS上にファイルをアップしておけば、Tez上で動作させても問題なく可能でしょう。

ただ、特定のファイルを扱うためにクラスが設定して指定されてロードされるケース、
例えばLzoなんかをどうやって設定すればいいのかについては疑問が残ります。

ですので、実際にやってみよう、という形です。

### 必要環境／パッケージのインストール
前提とする環境として、Lzoを含むGPLEXTRAのParcelを導入しておく必要があります。

Cloudera Managerのトップメニューから「Parcel」＞「設定」を選択し、下記のリポジトリを追加します。
https://archive.cloudera.com/gplextras5/parcels/{latest_supported}

下記キャプチャのように追加される形になりますね。
![ParcelSetting.JPG](https://qiita-image-store.s3.amazonaws.com/0/9616/ad97210a-d7e2-697c-397a-bc1a2873f8fe.jpeg)

追加した上で「新しいPercel のチェック」を実行すると、「GPLEXTRAS」が一覧に出てきますので、
それを「ダウンロード」＞「配布」＞「アクティブ化」と実行して有効化しておきます。

### パッケージに対する設定実施
パッケージがインストールできたので、次は設定です。
該当のクラスタで「HDFS」＞「設定」を選択し、下記のように設定項目に値を追加します。

- 設定項目：io.compression.codecs
- 追加内容：com.hadoop.compression.lzo.LzoCodec
- 追加内容：com.hadoop.compression.lzo.LzopCodec

![HDFSSetting.JPG](https://qiita-image-store.s3.amazonaws.com/0/9616/b6f92a5b-8d34-a4ab-fa23-0814290f7dd6.jpeg)

追加したら必要なサービスを再起動しておきます。

すると、下記のファイル中の設定に反映されていることがわかります。
- /etc/hadoop/conf/core-site.xml

### 読み込み用ディレクトリ／ファイルの作成
実際に読み込むためのファイルとして、前回使用した `data_noheader.csv` を圧縮して使用します。
下記コマンドを実行し、圧縮ファイルを作成します。

```bash
$ sudo yum install -y lzop
$ lzop data_noheader.csv
```

作成されたファイル( `data_noheader.csv.lzo` )をHDFSの `/user/pub/example-lzo` 配下にアップしておきます。

### Pigスクリプトの実行＞失敗
Pigスクリプトを下記のように修正し、実行します。

```bash:test-lzo-count.pig
loadresult = LOAD '/user/pub/example-lzo';
limitedresult = LIMIT loadresult 10;
logs_count = FOREACH (GROUP loadresult ALL) GENERATE COUNT(loadresult);
DUMP logs_count
DUMP limitedresult;
```

```bash
/opt/pig/bin/pig -x tez -f test-lzo-count.pig
```

実行すると、案の定というか、下記のエラーが発生して実行は失敗します。

```shell-session
/opt/pig/bin/pig -x tez -f test-lzo-count.pig
2016-04-24 18:44:47,043 [PigTezLauncher-0] INFO  org.apache.pig.backend.hadoop.executionengine.tez.TezJob - DAG Status: status=FAILED, progress=TotalTasks: 2 Succeeded: 0 Running: 0 Failed: 1 Killed: 1 FailedTaskAttempts: 4, diagnostics=Vertex failed, vertexName=scope-10, vertexId=vertex_1461490474710_0001_1_00, diagnostics=[Task failed, taskId=task_1461490474710_0001_1_00_000000, diagnostics=[TaskAttempt 0 failed, info=[Error: exceptionThrown=java.lang.IllegalArgumentException: Compression codec com.hadoop.compression.lzo.LzoCodec not found.
        at org.apache.hadoop.io.compress.CompressionCodecFactory.getCodecClasses(CompressionCodecFactory.java:135)
        at org.apache.hadoop.io.compress.CompressionCodecFactory.<init>(CompressionCodecFactory.java:175)
        at org.apache.hadoop.mapreduce.lib.input.LineRecordReader.initialize(LineRecordReader.java:87)
        at org.apache.pig.backend.hadoop.executionengine.mapReduceLayer.PigRecordReader.initialize(PigRecordReader.java:181)
        at org.apache.tez.mapreduce.lib.MRReaderMapReduce.setupNewRecordReader(MRReaderMapReduce.java:152)
        at org.apache.tez.mapreduce.lib.MRReaderMapReduce.setSplit(MRReaderMapReduce.java:85)
        at org.apache.tez.mapreduce.input.MRInput.initFromEventInternal(MRInput.java:614)
        at org.apache.tez.mapreduce.input.MRInput.processSplitEvent(MRInput.java:566)
        at org.apache.tez.mapreduce.input.MRInput.handleEvents(MRInput.java:530)
        at org.apache.tez.runtime.LogicalIOProcessorRuntimeTask.handleEvent(LogicalIOProcessorRuntimeTask.java:631)
        at org.apache.tez.runtime.LogicalIOProcessorRuntimeTask.access$600(LogicalIOProcessorRuntimeTask.java:98)
        at org.apache.tez.runtime.LogicalIOProcessorRuntimeTask$1.runInternal(LogicalIOProcessorRuntimeTask.java:694)
        at org.apache.tez.common.RunnableWithNdc.run(RunnableWithNdc.java:35)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassNotFoundException: Class com.hadoop.compression.lzo.LzoCodec not found
        at org.apache.hadoop.conf.Configuration.getClassByName(Configuration.java:1980)
        at org.apache.hadoop.io.compress.CompressionCodecFactory.getCodecClasses(CompressionCodecFactory.java:128)
        ... 13 more
(省略)
]], Vertex did not succeed due to OWN_TASK_FAILURE, failedTasks:1 killedTasks:0, Vertex vertex_1461490474710_0001_1_00 [scope-10] killed/failed due to:null]
Vertex killed, vertexName=scope-11, vertexId=vertex_1461490474710_0001_1_01, diagnostics=[Vertex received Kill while in RUNNING state., Vertex did not succeed due to OTHER_VERTEX_FAILURE, failedTasks:0 killedTasks:1, Vertex vertex_1461490474710_0001_1_01 [scope-11] killed/failed due to:null]
DAG did not succeed due to VERTEX_FAILURE. failedVertices:1 killedVertices:1, counters=Counters: 4
        org.apache.tez.common.counters.DAGCounter
                NUM_FAILED_TASKS=4
                TOTAL_LAUNCHED_TASKS=4
                AM_CPU_MILLISECONDS=1730
                AM_GC_TIME_MILLIS=56
```

PigTezLauncherの段階でHadoopの設定ファイルを読み込んだ際にこけているようですね。
かつ、TezのYARNコンテナでも同種のエラーは発生するでしょうから、下記の対応が必要になりそうです。

- Pig起動プロセスのクラスパスにLzoライブラリを追加
- TezのYARNコンテナのクラスパスにLzoライブラリを追加

### Pig on TezにLzoライブラリの設定を追加

- Pig起動プロセスのクラスパスにLzoライブラリを追加

これはPigの起動スクリプトの頭に追加しても、
Tezのライブラリディレクトリにシンボリックリンクをはってもどちらでもいいのですが、
Pigの起動スクリプトに追加したのは完全にTezの設定ですので、Tezを使うのは確定だろう、
ということで後者で実施します。

```
$ ln -s /opt/cloudera/parcels/GPLEXTRAS/lib/hadoop/lib/hadoop-lzo.jar /opt/tez-lib/hadoop-lzo.jar
```

- TezのYARNコンテナのクラスパスにLzoライブラリを追加

`/opt/tez-conf/tez-site.xml` に下記の設定を追加します。

```xml:tez-site.xml
  <property>
      <name>tez.cluster.additional.classpath.prefix</name>
      <value>/opt/cloudera/parcels/GPLEXTRAS/lib/hadoop/lib/hadoop-lzo.jar</value>
  </property>
```

### Pigスクリプトの再実行
再度**hdfsユーザ**でPigスクリプトを実行してみます。すると・・・

```shell-session:hdfsユーザでの実行
/opt/pig/bin/pig -x tez -f test-lzo-count.pig
（省略）
2016-04-24 20:03:40,460 [main] INFO  org.apache.pig.tools.pigstats.tez.TezPigScriptStats - Script Statistics:

       HadoopVersion: 2.6.0-cdh5.7.0
          PigVersion: 0.15.0
          TezVersion: 0.7.0
              UserId: hdfs
            FileName: /tmp/test-lzo-count.pig
           StartedAt: 2016-04-24 20:03:28
          FinishedAt: 2016-04-24 20:03:40
            Features: GROUP_BY

Success!

DAG PigLatin:test-lzo-count.pig-0_scope-0:
       ApplicationId: job_1461490474710_0014
  TotalLaunchedTasks: 2
       FileBytesRead: 99
    FileBytesWritten: 67
       HdfsBytesRead: 61480
    HdfsBytesWritten: 7

Input(s):
Successfully read 1744 records (61480 bytes) from: "/user/pub/example-lzo"

Output(s):
Successfully stored 1 records (7 bytes) in: "hdfs://hadoophost01:8020/tmp/temp-1823417929/tmp552056591"
(1744)
(1871-01-01,4.44,0.26,0.4,12.46,5.32,84.52,4.95,7.61,,,,,,,)
(1871-02-01,4.5,0.26,0.4,12.84,5.32,83.12,4.8,7.39,,,,,,,)
(1871-03-01,4.61,0.26,0.4,13.03,5.33,83.91,4.73,7.28,,,,,,,)
(1871-04-01,4.74,0.26,0.4,12.56,5.33,89.54,4.91,7.56,,,,,,,)
(1871-05-01,4.86,0.26,0.4,12.27,5.33,93.95,5.03,7.73,,,,,,,)
(1871-06-01,4.82,0.26,0.4,12.08,5.34,94.64,5.11,7.85,,,,,,,)
(1871-07-01,4.73,0.26,0.4,12.08,5.34,92.87,5.11,7.85,,,,,,,)
(1871-08-01,4.79,0.26,0.4,11.89,5.34,95.56,5.19,7.98,,,,,,,)
(1871-09-01,4.84,0.26,0.4,12.18,5.35,94.29,5.07,7.79,,,,,,,)
(1871-10-01,4.59,0.26,0.4,12.37,5.35,88.04,4.99,7.67,,,,,,,)
```

実行に成功しました。
前回と比べると、読込ファイルサイズが下記のように変わっているので、確かに圧縮したファイルを読み込んだことがわかります。

```shell-session:前回結果
Successfully read 1744 records (116662 bytes) from: "/user/pub/example-data"
```

```shell-session:今回結果
Successfully read 1744 records (61480 bytes) from: "/user/pub/example-lzo"
```

### が、しかし・・・？
ただ、ここで上手く行ったと思って別のユーザ（今回はビルド用ユーザの**build**）で実行してみると、
何故か読込に失敗する事象が発生しました。

```shell-session:buildユーザでの実行＞失敗
/opt/pig/bin/pig -x tez -f test-lzo-count.pig
2016-04-24 20:13:44,815 [main] INFO  org.apache.pig.tools.pigstats.tez.TezPigScriptStats - Script Statistics:

       HadoopVersion: 2.6.0-cdh5.7.0
          PigVersion: 0.15.0
          TezVersion: 0.7.0
              UserId: build
            FileName: test-lzo-count.pig
           StartedAt: 2016-04-24 20:13:26
          FinishedAt: 2016-04-24 20:13:44
            Features: GROUP_BY

Failed!

DAG PigLatin:test-lzo-count.pig-0_scope-0:
       ApplicationId: job_1461490474710_0015
  TotalLaunchedTasks: 4
       FileBytesRead: 0
    FileBytesWritten: 0
       HdfsBytesRead: 0
    HdfsBytesWritten: 0

Input(s):
Failed to read data from "/user/pub/example-lzo"

Output(s):
Failed to produce result in "hdfs://hadoophost01:8020/tmp/temp-1843801261/tmp-1479312519"

2016-04-24 20:13:44,912 [main] ERROR org.apache.pig.tools.grunt.Grunt - ERROR 1066: Unable to open iterator for alias logs_count. Backend error : Vertex failed, vertexName=scope-10, vertexId=vertex_1461490474710_0015_1_00, diagnostics=[Task failed, taskId=task_1461490474710_0015_1_00_000000, diagnostics=[TaskAttempt 0 failed, info=[Error: exceptionThrown=java.lang.IllegalArgumentException: Compression codec com.hadoop.compression.lzo.LzoCodec not found.
```

### 違いはどこにある？

どこに違いがあるのかざっと追ってみると、下記の２つの時点で差分があることがわかりました。

- 1. YARNコンテナ起動時の環境変数設定（YARNの一時ファイル）
  - buildユーザで実行した側は `tez.cluster.additional.classpath.prefix` の設定がない
- 2. Pigから呼び出されたTezClient中の処理（リモートデバッグ）
  - buildユーザで実行した側は `tez.cluster.additional.classpath.prefix` の設定がない

つまりはHadoopコマンド > Pig > Tezとなる中で設定が欠落しているのですが、
ざっとデバッグした時点では結論は見えずという状況。

ですので、Pigが読み込んでHadoopの設定に含めているファイルの `pig.properties` に設定を追記して試してみました。

```shell-session
$ echo "tez.cluster.additional.classpath.prefix=/opt/cloudera/parcels/GPLEXTRAS/lib/hadoop/lib/hadoop-lzo.jar" >> /opt/pig/conf/pig.properties
```

### 再度実行

すると・・・？

```shell-session:buildユーザでの実行＞成功
/opt/pig/bin/pig -x tez -f test-lzo-count.pig
2016-04-24 22:16:17,439 [main] INFO  org.apache.pig.tools.pigstats.tez.TezPigScriptStats - Script Statistics:

       HadoopVersion: 2.6.0-cdh5.7.0
          PigVersion: 0.15.0
          TezVersion: 0.7.0
              UserId: build
            FileName: test-lzo-count.pig
           StartedAt: 2016-04-24 22:16:06
          FinishedAt: 2016-04-24 22:16:17
            Features: GROUP_BY

Success!

DAG PigLatin:test-lzo-count.pig-0_scope-0:
       ApplicationId: job_1461490474710_0025
  TotalLaunchedTasks: 2
       FileBytesRead: 99
    FileBytesWritten: 67
       HdfsBytesRead: 61480
    HdfsBytesWritten: 7

Input(s):
Successfully read 1744 records (61480 bytes) from: "/user/pub/example-lzo"

Output(s):
Successfully stored 1 records (7 bytes) in: "hdfs://hadoophost01:8020/tmp/temp-1901693688/tmp-778527532"

(1744)
(1871-01-01,4.44,0.26,0.4,12.46,5.32,84.52,4.95,7.61,,,,,,,)
(1871-02-01,4.5,0.26,0.4,12.84,5.32,83.12,4.8,7.39,,,,,,,)
(1871-03-01,4.61,0.26,0.4,13.03,5.33,83.91,4.73,7.28,,,,,,,)
(1871-04-01,4.74,0.26,0.4,12.56,5.33,89.54,4.91,7.56,,,,,,,)
(1871-05-01,4.86,0.26,0.4,12.27,5.33,93.95,5.03,7.73,,,,,,,)
(1871-06-01,4.82,0.26,0.4,12.08,5.34,94.64,5.11,7.85,,,,,,,)
(1871-07-01,4.73,0.26,0.4,12.08,5.34,92.87,5.11,7.85,,,,,,,)
(1871-08-01,4.79,0.26,0.4,11.89,5.34,95.56,5.19,7.98,,,,,,,)
(1871-09-01,4.84,0.26,0.4,12.18,5.35,94.29,5.07,7.79,,,,,,,)
(1871-10-01,4.59,0.26,0.4,12.37,5.35,88.04,4.99,7.67,,,,,,,)
```

`pig.properties` に設定した分はbuildユーザでも問題なく読み込むことがわかりました。
差分が発生する原因はわかりませんが、とりあえず解消はできた、という形になりますね。
クラスパスからHadoop設定オブジェクトを生成するときの挙動の差のようですが、
これは気長に追ってみることにします・・・

### まとめ
- Pig on TezにLzoを追加する場合は下記の対応が必要
  - Pig on Tez起動時のクラスパスにLzoライブラリを含める
  - Tez動作時の追加クラスパス( `tez-site.xml` )にLzoライブラリを含める
      - ただし、一部のユーザでは `tez-site.xml` に含めても動作しない（原因が現状不明）
      - 上記の場合、 `pig.properties` に記述すれば動作する。