Hadoop 3 の GA を記念して S3Guard を試してみる

本記事は個人の見解であり、所属組織の立場、意見を代表するものではありません.

[Distributed computing (Apache Hadoop, Spark, Kafka, ...) Advent Calendar 2017](https://qiita.com/advent-calendar/2017/distributed-computing) の 12/18 分です。

ついに Hadoop 3 が GA になりましたね！
本記事では Hadoop 3 の GA を記念して、新しい機能である S3Guard を試してみます。

# S3Guard とは
Amazon S3 は広く知られている通り、整合性モデルとして以下の特徴をもちます。

- 新しいオブジェクトの PUT に対する 書き込み後の読み取り整合性 (Read-after-write consistency)
- 上書き PUT および DELETE に対する 結果整合性 (eventual consistency)

Hadoop から S3 を DFS として使う場合 S3A などを使う※わけですが、当然このような結果整合性の影響を受けます。
※ちなみに EMR では S3A ではなく EMRFS が推奨されています。

S3Guard とは S3A の拡張で、S3 上のオブジェクトのメタデータを別途保存・活用することで、Hadoop に整合性をもったビューを提供するための新機能です。
※他にもパフォーマンス改善を目的に含みますが、今回の説明では割愛します。

オフィシャルドキュメントはこちら。

- S3Guard: Consistency and Metadata Caching for S3A  http://hadoop.apache.org/docs/r3.0.0/hadoop-aws/tools/hadoop-aws/s3guard.html


Hadoop と S3 の関係性については imai_factory 先生が去年の Advent Calendar で詳しく解説してくれてますので、そちらをご参照ください。

- HadoopとS3 https://qiita.com/imaifactory/items/6866a5c5d8cc363ace12

# Hadoop 3 環境の構築
では、早速環境を準備しましょう。
今回は以下の環境に Single Node Cluster を構築します。
- Amazon Linux 2 LTS Candidate AMI 2017.12.0.20171212.2 x86_64 HVM GP2
- c5.xlarge
- IAM ロール/インスタンスプロファイル付与 (DynamoDB/S3 のアクションをすべて許可) 

まずは OpenJDK 1.8.0 をインストールして JAVA_HOME を export。

```Bash
$ sudo yum install java-1.8.0-openjdk
$ export JAVA_HOME=/usr/lib/jvm/jre/
```

Hadoop 3 GA パッケージをダウンロードして配置。今回は riken さんのミラーを使います。

```Bash
$ wget http://ftp.riken.jp/net/apache/hadoop/common/hadoop-3.0.0/hadoop-3.0.0.tar.gz
$ tar -zxvf hadoop-3.0.0.tar.gz
$ cd hadoop-3.0.0/
```

では早速サンプルを動かしてみましょう。正規表現にマッチしたものを表示します。

```Bash
$ mkdir /tmp/input
$ cp etc/hadoop/*.xml /tmp/input/
$ bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.0.0.jar grep /tmp/input /tmp/output 'dfs[a-z.]+'
$ cat /tmp/output/*
1       dfsadmin
```
うまく動きました。

# S3A
次に、S3A を使って S3 にアクセスしてみます。
S3A を使うには hadoop-aws-3.0.0.jar をクラスパスに含める必要があるので、hadoop-env.sh を以下のように編集します。

```Bash:etc/hadoop/hadoop-env.sh
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:share/hadoop/tools/lib/*
```

これだけですね。では早速 S3A を使って S3 上の入力データを S3 に出力してみます。

```Bash
$ bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.0.0.jar wordcount s3a://us-east-1.elasticmapreduce.samples/wordcount/data/ s3a://sample-bucket/hadoop3/output_wc/
$ bin/hdfs dfs -cat s3a://sample-bucket/hadoop3/output_wc/*
```
できました。WordCount の結果がどばーっと出ました。（ここでは長いので省略しています。）

# S3Guard
ようやく本題。S3Guard を試してみます。
S3Guard を使うには core-site.xml に設定を追加する必要があります。
S3Guard ではメタデータ保存用のデータストアとして DynamoDB を使用します。S3 を使ってるんだから AWS アカウントをもってるだろうということで、DynamoDB を使うのは自然ですね。
ちなみに、EMR には EMRFS Consistent View という機能があり、そちらでも DynamoDB が使用されています。

```Bash:etc/hadoop/core-site.xml
<property>
    <name>fs.s3a.metadatastore.impl</name>
    <value>org.apache.hadoop.fs.s3a.s3guard.DynamoDBMetadataStore</value>
</property>

<property>
    <name>fs.s3a.s3guard.ddb.table</name>
    <value>s3guard-table</value>
</property>

<property>
  <name>fs.s3a.s3guard.ddb.region</name>
  <value>us-east-1</value>
</property>

<property>
    <name>fs.s3a.s3guard.ddb.table.create</name>
    <value>true</value>
</property>
```

それではサンプルジョブを実行します。

```Bash
$ bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.0.0.jar wordcount s3a://us-east-1.elasticmapreduce.samples/wordcount/data/ s3a://sample-bucket/hadoop3/output_wc_s3guard/
$ bin/hdfs dfs -cat s3a://sample-bucket/hadoop3/output_wc_s3guard/*
```
できました。またもや WordCount の結果がどばーっと出ました。（長いので省略します。）

。。。と、S3Guard のあり/なしの影響がよくわからないですよね。
整合性が影響するようなワークロードじゃないと効果のほどはかなりわかりづらいです。
同時に複数のジョブがアドホックに走る状況で、結果整合性を許容できないようなワークロードで初めて生きてくるわけです。

ただ、DynamoDB に実際に保存されたメタデータの状態には興味が出てきますよね。
そこで、ジョブの実行が終わったこの時点のメタデータを見てみます。

まずはテーブルがちゃんとできているか Describe してみましょう。

```Bash
$ aws dynamodb describe-table --table-name s3guard-table
{
    "Table": {
        "TableArn": "arn:aws:dynamodb:us-east-1:123456789101:table/s3guard-table",
        "AttributeDefinitions": [
            {
                "AttributeName": "child",
                "AttributeType": "S"
            },
            {
                "AttributeName": "parent",
                "AttributeType": "S"
            }
        ],
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "WriteCapacityUnits": 100,
            "ReadCapacityUnits": 500
        },
        "TableSizeBytes": 0,
        "TableName": "s3guard-table",
        "TableStatus": "ACTIVE",
        "TableId": "a0227da8-6f98-40b7-b973-67e83a206532",
        "KeySchema": [
            {
                "KeyType": "HASH",
                "AttributeName": "parent"
            },
            {
                "KeyType": "RANGE",
                "AttributeName": "child"
            }
        ],
        "ItemCount": 0,
        "CreationDateTime": 1513514910.2
    }
}
```

ちゃんとできてました。
それでは、このテーブルの中身をスキャンしてみましょう。
（出力がかなり長いので興味がない方は読み飛ばしてください。）

```Bash
$ aws dynamodb scan --table-name s3guard-table
{
    "Count": 24,
    "Items": [
        {
            "is_dir": {
                "BOOL": true
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples"
            },
            "child": {
                "S": "wordcount"
            }
        },
        {
            "mod_time": {
                "N": "1513514946338"
            },
            "is_deleted": {
                "BOOL": true
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard/_temporary"
            },
            "child": {
                "S": "0"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "0"
            }
        },
        {
            "mod_time": {
                "N": "1513514944878"
            },
            "is_deleted": {
                "BOOL": true
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard/_temporary/0/_temporary/attempt_local1505268872_0001_r_000000_0"
            },
            "child": {
                "S": "part-r-00000"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "0"
            }
        },
        {
            "is_dir": {
                "BOOL": true
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount"
            },
            "child": {
                "S": "data"
            }
        },
        {
            "mod_time": {
                "N": "1513514948217"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard"
            },
            "child": {
                "S": "_SUCCESS"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1513514946328"
            },
            "is_deleted": {
                "BOOL": true
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard"
            },
            "child": {
                "S": "_temporary"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "0"
            }
        },
        {
            "mod_time": {
                "N": "1513514944034"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard"
            },
            "child": {
                "S": "part-r-00000"
            },
            "file_length": {
                "N": "733216"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1513514946347"
            },
            "is_deleted": {
                "BOOL": true
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard/_temporary/0"
            },
            "child": {
                "S": "_temporary"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "0"
            }
        },
        {
            "mod_time": {
                "N": "1513514946357"
            },
            "is_deleted": {
                "BOOL": true
            },
            "parent": {
                "S": "/sample-bucket/hadoop3/output_wc_s3guard/_temporary/0/_temporary"
            },
            "child": {
                "S": "attempt_local1505268872_0001_r_000000_0"
            },
            "file_length": {
                "N": "0"
            },
            "block_size": {
                "N": "0"
            }
        },
        {
            "mod_time": {
                "N": "1406157796000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0001"
            },
            "file_length": {
                "N": "2392524"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157796000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0002"
            },
            "file_length": {
                "N": "2396618"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157796000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0003"
            },
            "file_length": {
                "N": "1593915"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157796000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0004"
            },
            "file_length": {
                "N": "1720885"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157796000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0005"
            },
            "file_length": {
                "N": "2216895"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157797000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0006"
            },
            "file_length": {
                "N": "1906322"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0007"
            },
            "file_length": {
                "N": "1930660"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0008"
            },
            "file_length": {
                "N": "1913444"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0009"
            },
            "file_length": {
                "N": "2707527"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0010"
            },
            "file_length": {
                "N": "327050"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0011"
            },
            "file_length": {
                "N": "8"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "mod_time": {
                "N": "1406157798000"
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/us-east-1.elasticmapreduce.samples/wordcount/data"
            },
            "child": {
                "S": "0012"
            },
            "file_length": {
                "N": "8"
            },
            "block_size": {
                "N": "33554432"
            }
        },
        {
            "is_dir": {
                "BOOL": true
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/sample-bucket"
            },
            "child": {
                "S": "hadoop3"
            }
        },
        {
            "table_created": {
                "N": "1513514920327"
            },
            "table_version": {
                "N": "100"
            },
            "parent": {
                "S": "../VERSION"
            },
            "child": {
                "S": "../VERSION"
            }
        },
        {
            "is_dir": {
                "BOOL": true
            },
            "is_deleted": {
                "BOOL": false
            },
            "parent": {
                "S": "/sample-bucket/hadoop3"
            },
            "child": {
                "S": "output_wc_s3guard"
            }
        }
    ],
    "ScannedCount": 24,
    "ConsumedCapacity": null
}

```

入出力の両方に関係する S3 オブジェクトのメタデータが格納されていることが確認できます。

# おわりに
今回は Hadoop 3 GA 記念ということで S3Guard を触ってみました。
Hadoop 3 には他にも面白い機能がたくさんありますので、どんどん試して使っていきたいですね。