HadoopとS3


[Distributed computing (Apache Hadoop, Spark, ...) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/distributed-computing)の12/25分です。もともとYARNのApplication Timeline Server v2について書こうと思っていたのですが、気が変わってHadoopとS3の関係性について書いていこうと思います。もし期待していた方がいらっしゃったらごめんなさい。（ATSについては機会があればまた書きたいと思います。)

## 背景

S3について書こうとおもったのは、下記のような仕組みを作っている中でいろいろ思うところがあったからです。

![Kobito.cazCae.png](https://qiita-image-store.s3.amazonaws.com/0/10292/8afe8dfd-8a2c-314e-8cf2-f945f9217262.png "Kobito.cazCae.png")


システムがS3に吐き出す様々なログ、データやメトリクスをZepplinで可視化するための仕組みです。永続化したいデータはS3にしか置かず、SparkやZeppelinなどのコンポーネントはステートレスかつ拡張、もしくは別の計算フレームワークや可視化フレームワークへの交換が用意であることにポイントを置いて設計しています。

また、分析・可視化サイドのキャパシティ面での拡張性については、ZeppelinのバックエンドのSparkクラスタを大きくするのではなく、下記のようにユーザーサイドの分析・可視化環境そのものを増やしてしまえるような運用を考えています。なお、分析環境1セットはEC2一台で構成しており、環境ごとの必要キャパシティの調整はインスタンスタイプの大小によって実現しています。

![Kobito.3qjGmY.png](https://qiita-image-store.s3.amazonaws.com/0/10292/4a8f91db-e635-20d9-48e3-97a8e945a3a1.png "Kobito.3qjGmY.png")


この拡張方式をとるメリットとしては、分析や可視化にこの仕組を利用するユーザー同士でお互いを意識する必要がなく、自分のためだけの環境でアグレッシブにスクラップ&ビルドができるというのがポイントです。前述のようにEC2一台で環境が1セットそろうので、一人のひとが用途ごとに複数の環境を持つことも比較的容易です。また、それぞれが必要とするキャパシティが大きく異なる場合に、それぞれに対して最適なキャパシティを割り当てることができることができます。

このようにストレージと計算リソースを切り離すことで、計算リソース側の拡張性は比較的きれいに実現できたのですが、ストレージのS3部分に課題が残りました。それはS3のスロットリング問題です。

S3は、ある一定のアクセスレートを超えたAPIへのアクセスに対して"Rate Limit Exceeded"というエラーを返します。これを一般的にスロットリングと呼んでいます。この状態になると、ある一定の時間待ってから再度アクセスしてやる必要があります。

このあたりについて詳細に知りたい方は下記を読んでみるといいと思います。

[リクエスト率およびリクエストパフォーマンスに関する留意事項](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/request-rate-perf-considerations.html)

ということで背景が長くなりましたが、現在利用している、Sparkベースの仕組みからS3をどうやったら最大限に活用できるのか、パフォーマンスを出し切れるのかというのを調査するにあたって、HadoopとS3の関係性について改めておさらいしてみたのが今回のポストです。

## HadoopとS3の関係性の歴史

S3AとはHadoop関連エコシステムからS3上のオブジェクトにアクセスするためのドライバのようなもので、以下のようなURLスキームで利用します。

```
s3a://bucket_name/path/to/dir
```

ドライバはこれ以外にも`s3`、`s3n`というものがあり、歴史的にみるとs3, s3n, s3aという流れで進化をしてきています。

![Kobito.XQLpMe.png](https://qiita-image-store.s3.amazonaws.com/0/10292/fec28245-edfc-36b7-c13d-dfa779884c51.png "Kobito.XQLpMe.png")

上記の画像はその歴史を樹形図にしたものでHortonworksのブログ([THE HISTORY OF APACHE HADOOP’S SUPPORT FOR AMAZON S3](http://hortonworks.com/blog/history-apache-hadoops-support-amazon-s3/))から借りてきたものです。

内容をざっくりまとめると以下のようになります。

1. 2006年(Amazon S3がリリースされた年です)、S3をHadoopファイルシステムとして取り扱いたいという要望のもとHADOOP-574に議論が進み、S3://ファイルシステムが開発された。
2. S3://ファイルシステムはS3上に当該ファイルシステムを通してしか読み書きのできない形でオブジェクトを書いてしまうという問題を解決するために2008年にS3N(S3 Native Filesystem)://ファイルシステムが開発された(HADOOP-931)。
3. いっぽう、AmazonはS3://ファイルシステムにクローズドな改善を入れ続け、非常に便利なものになっていった。S3N://ファイルシステムよりも機能開発や改善の進みが早くなっていった。
4. S3NやS3(Amazon版)は便利になってきていたが、Consistencyについて問題があった(S3はオブジェクトの新規作成オペレーションに対して強一貫性を提供しますが、上書きや削除は弱一貫性を持ったオペレーションになります)。これを解決するためにNetflixはS3mperという独自ファイルシステムを作った。
5. 2014年、コミュニティからの貢献によってスケーラビリティや性能を改善するためのS3A://ファイルシステムが取り込まれた(HADOOP-10400)。
6. 現在、このS3A://に対してよりスケーラビリティや安定性を高めるための改善や、前述のConsistency問題への対処のための改善が続けられている。

という感じで、現在Hadoopコミュニティの最新のファイルシステムはS3Aであり、現在も改善の努力が続けられています。ここから先はS3Aについて書いていきます。

## S3A Phase1

[Incorporate new S3A FileSystem implementation](https://issues.apache.org/jira/browse/HADOOP-10400)

ここからS3Aの特徴や、解決したかった問題をまとめてみます。

- 並列コピーのサポートによる大きなファイルのコミットの性能改善
- オブジェクト全体をダウンロードせずにシークを行えるようにする
- S3Nとの完全な互換性
- IAM role-based認証のサポート
- S3へのオブジェクト配置の際にACLを設定できるようにする
- ベターなエラーハンドリング

個人的に大きな改善だと思うのは、部分的なシーク（おそらくS3がサポートしているS3のRange GETを使っているはず）によるSplitの生成における性能改善、IAM role-based認証のサポートが大きいなと感じます。

また、個人的にふだん目を向けていないところですが並列コピーによる大きなファイルのアップロード高速化（ファイル単位での並列化？）が、JIRAでは一番大きく取り扱われています。

また、残る課題として、S3はオブジェクトのリネームをサポートしていないので、リネームを伴うようなオペレーション（distcpなど）におけるタイムアウトが発生する可能性があるという話に触れられています。Hadoopはコピー中のファイルを`filename.COPYING`というファイル名をつけておき、コミット時に`filename`にリネームするようです（知りませんでした）。先に語られているとおり、S3はリネームはサポートしておらず、コピー&削除になるので大きな時間がかかるためです。（現在、リネームというAPIはありますが、バックエンドでコピー&削除が動いている。はず。）

## S3A Phase2

[Über-jira: S3a phase II: robustness, scale and performance](https://issues.apache.org/jira/browse/HADOOP-11694)

S3Aのフェーズ2です。これは現在進行中プロジェクトで、ターゲットバージョンは2.8とされています。Hadoop2.7以降で発見されたもろもろの改善点について取り組むためのチケットです。概要であまり多くは語られていないので、関連付けられているチケットやサブタスクから気になるものを個人的にピックしてみます。

- [Support lazy seek in S3AInputStream](https://issues.apache.org/jira/browse/HADOOP-12444): seek()のたびにS3へのコネクションを張るのをやめた
- [add option for lazy open() on s3a](https://issues.apache.org/jira/browse/HADOOP-13009): seek()が走るまでS3へのコネクションを張らないオプション。しかし、ファイルが存在しなくてもopen()が失敗しなくなるので注意。
- [S3a Forward seek in stream length to be configurable](https://issues.apache.org/jira/browse/HADOOP-13047): seek()時にある程度の読み飛ばしをするとclose()&reconnect()してしまっていたことがわかったので、スレッシュホールドを設定可能に。余分なデータを読んでしまっても、TCPのスロースタートを考慮すると効率がいい場合がある。
- [switch hadoop-aws back to using the (heavy) amazon-sdk JAR](https://issues.apache.org/jira/browse/HADOOP-13078): Hadoop2.8以降、amazon-aws-sdkをやめてamazon-s3-sdkに切り替える。
- [Customize User-Agent header sent in HTTP requests by S3A.](https://issues.apache.org/jira/browse/HADOOP-13122): S3へのアクセス時のUAヘッダをカスタム可能にし、トラブルシューティングを容易に。
- [S3ABlockOutputStream to support huge (many GB) file writes](https://issues.apache.org/jira/browse/HADOOP-13560)

ざっと見た感じ、Phase2は効率の改善などが多そうです。（私が個人的にその辺が気になってピックした、という偏りもありますのでご注意を。）

## S3A Phase3

[Über-jira: S3a phase III: scale and tuning](https://issues.apache.org/jira/browse/HADOOP-13204)

Phase3はHadoop2.8以降で発見された/されるであろう課題を2.9にフィードバックするためのプロジェクトです。カスタマイズ性、パフォーマンス改善、運用性、エラーハンドリングなどが主要な取組分野として触れられています。

こちらも個人的にサブタスクなどをピックしてみます。

- [S3A to support custom retry policies; maybe failfast on unknown host](https://issues.apache.org/jira/browse/HADOOP-13205)
- [S3AInputStream to use a retry policy on read failures](https://issues.apache.org/jira/browse/HADOOP-13664)

個人的に一番うれしい変更です。S3アクセス時のスロットリング問題について、aws-sdkのリトライポリシーを変更可能にしてくれるというものです。sdkのデフォルト値はしっかりと確認していませんが、エクスポネンシャルバックオフをしながら何度かリトライし、失敗回数がスレッシュホールドを超えたらExceptionという方式になっています。これを「エクスポネンシャルバックオフ時にログを吐く」だったり「一度もバックオフせずにException」など設定できるようになります。S3アクセス時の速度の調整やトラブルシューティングが非常にやりやすくなると思います。

- [S3AFastOutputStream to implement flush()](https://issues.apache.org/jira/browse/HADOOP-13568): flush()が実装されますね。
- [S3a rename() to copy files in a directory in parallel](https://issues.apache.org/jira/browse/HADOOP-13600): これまでディレクトリのリネームは、内部のファイルひとつずつコピー&削除してきましたが、これを並列にやろうというお話。
- [S3a Multipart Committer (avoid rename)](https://issues.apache.org/jira/browse/HADOOP-13912): 大きなファイルをS3にコピーするときにファイルをアップロードしてリネームするのではなくて、S3のマルチパートアップロードAPIを使うようにしましょうという話。S3のマルチパートAPIは`マルチパートアップロードの初期化`、`各パートの実際のアップロード`、`マルチパートアップロードのファイナライズ`という3つのサブAPIにオペレーションが別れているので、これまでと同じようなことが、より効率的に（S3上でリネームせずに）実現できるようになります。ファイルアップロードが早くなりそうで期待あげ。

Phase3が完了するとS3のエラーハンドリングやアクセス速度の調整が非常にやりやすくなると思います。楽しみ。

## S3Guard

さらにPhase3の関連プロジェクトで、S3Guardというものがあります。

[S3Guard: Improved Consistency for S3A](https://issues.apache.org/jira/browse/HADOOP-13345)

これはS3mperのようなConsistency、一貫性問題について取り組むためのプロジェクトで、[S3mper](https://github.com/Netflix/s3mper)や[EMRFS](http://docs.aws.amazon.com/ja_jp/ElasticMapReduce/latest/DeveloperGuide/emr-fs.html)と同じく、メタデータストアとしてDynamoDBを利用することによって強い一貫性を実現するというプロジェクトです。

![Kobito.Zo14do.png](https://qiita-image-store.s3.amazonaws.com/0/10292/947a3804-f8c5-147b-573a-249a0e97ebff.png "Kobito.Zo14do.png")

画像は本プロジェクトのJIRAに添付されている[デザインドキュメント](https://issues.apache.org/jira/secure/attachment/12821464/S3GuardImprovedConsistencyforS3AV2.pdf)から借りました。

create()やdelete()などは先にメタデータを書いてから実際のデータをS3に反映、listStatus()などのオペレーションはメタデータだけで完結させるという方式になっています。また、デザインドキュメント中にでも議論がされていますが、メタデータはあくまでS3上の実データのプロジェクション、キャッシュであり、listStatus()など参照系のオペレーション時にはS3のデータを正として、それをキャッシュする戦略を取っています。

個人的にはあまり一貫性まわりで苦しんだことはなくて、使い所がピンときていないのですが（あまりがっつり使い込んでない・・・？）、このドキュメントでは使いどころとして以下のようなケースが語られています。

- DistCpを使ったクラウドバックアップからのリストア: S3に取られたバックアップをリストアしようと思ったときに、inconsistentな状態だと困りますよね、という話。いくらS3がeventual consistentだとはいえ、遅れても数秒〜十数秒（あまり見たこと無い）というレベルだと思うので、これにハマるケースは稀だと思いますが・・
- S3を共通のメインストレージとして使う場合: S3への更新が頻繁な場合、データ参照のタイミングによって見えるデータが変わってくる可能性があるのでそれを防ぎたいケースですね。こんかいのわたしのケースの場合、こういった問題にヒットする可能性はあります。これが問題になるかどうかはアプリケーションのSLAによるのかなと思います。データの精度100%を求めるアプリケーションであればS3Guardが必要ですね。
- S3を挟んだ多段ETL: これは確かに、中間データがInconsistentだったら大問題になりますね。

## まとめ

HadoopとS3の関係性やその歴史、最近のS3Aの進化についてまとめてみまし。個人的にはPhase3のリトライポリシーのカスタマイズ周りが非常に待ち遠しいです。（コントリビュートしろよって話ですね。はい。）

それから、今回のわたしのユースケースのような場合、そもそもS3へのリードアクセスを減らすためのアプローととしてLLAPを使うというのはとても有用なものだと思っています。最終的には下記のようなアーキテクチャにしていきたいなと思っています。

![Kobito.hxmcpX.png](https://qiita-image-store.s3.amazonaws.com/0/10292/e0e40daa-688b-cd1d-7dae-4911543fe41c.png "Kobito.hxmcpX.png")

いま実際LLAP周りでいろいろと格闘しているのですが、それはまた今後書きたいと思います。