データインターフェースとしてのHadoop ~HDFSとクラウドストレージと私~ (NTTデータ テクノロジーカンファレンス 2019 講演資料、2019/09/05)
- 1. © 2019 NTT DATA Corporation 1 © 2019 NTT DATA Corporation
NTTデータ テクノロジーカンファレンス 2019
データインターフェースとしてのHadoop
2019年9月5日
株式会社NTTデータ 岩崎正剛
- 2. © 2019 NTT DATA Corporation 2
クラウドサービスの発展により、大量のデータを格納して処理す
るための選択肢は多くなりました。
Hadoopは、分散ファイルシステム(HDFS)と各種クラウドスト
レージ等を透過的に併用して、データ処理を行うための機能を提
供しており、Sparkに代表される分散処理フレームワークのユー
ザは、あまり意識せずにこれを活用しています。
本セッションでは、Hadoopが提供するデータアクセスのための
インターフェースやモジュールに焦点をあて、データストアの併
用や使い分けの勘所などを解説します。
概要
- 4. © 2019 NTT DATA Corporation 4
Hadoop Distributed File System
Hadoop = 分散FS + 分散処理FW
Hadoopアプリケーション =
HDFS上のデータを処理するもの
HDFS
https://hadoop.apache.org/docs/r3.2.0/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html
- 5. © 2019 NTT DATA Corporation 5
core-site.xmlに書く基本的な設定項目
HDFSのアクセス先を書く
ネームサービス名
またはNameNodeのホスト名:port
hdfs://以外もある?
defaultではないFSもある?
fs.defaultFS
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster/</value>
</property>
- 6. © 2019 NTT DATA Corporation 6
ファイルのpathをURI形式で指定
schemeに応じてよしなにデータを読み書き
データストア間のデータコピー/移動にも便利
distcpなどでも同じ要領
schemeなしのただのpathだとfs.defaultFS
Hadoopのデータストア抽象化
$ hadoop fs -cp file:///a/b hdfs://ns1/a/
$ hadoop fs -cp hdfs://ns1/a/b hdfs://nn1:8020/a/
$ hadoop fs -cp webhdfs://nn1:9870/a/b s3a://bc/a/
- 7. © 2019 NTT DATA Corporation 7
Hadoop Compatible File Systems
Hadoop FileSystem API
Hadoop
Application
HDFS Local FS Amazon S3
Azure
Data Lake
Storage gen2
...
Distributed
FileSystem
Local
FileSystem
S3A
FileSystem
AzureBlob
FileSystem
SparkMapReduce
Spark
Application
MapReduce
Application
WebHdfs
FileSystem
Ozone
Ozone
FileSystem
- 8. © 2019 NTT DATA Corporation 8
Google Cloud Storage
https://github.com/GoogleCloudPlatform/bigdata-interop/tree/master/gcs
Oracle Cloud Infrastructure
https://github.com/oracle/oci-hdfs-connector
Ignite File System
https://github.com/apache/ignite/tree/master/modules/hadoop
サードパーティ製のFileSystem実装
- 9. © 2019 NTT DATA Corporation 9
FileSystem APIをユーザ向けに整理する意図
意図通りに普及/移行していない...
FileContextのドキュメントがない
できることに(ほとんど)差はない
Hadoopのコード自体が両方使っている
FileSystem実装を作るときにケアする必要あり
FileContext API(HADOOP-4952)
FileContext ctx = FileContext.getFileContext(uri);
FSDataInputStream is = ctx.create(path, ...);
ctx.setWorkingDir(path);
FSDataInputStream os = ctx.open(path, ...);
- 10. © 2019 NTT DATA Corporation 10
.jarにclasspathを通す
Configurationでschemeとクラス名を対応づけ
fs.foobar.impl => foobar://
see https://hadoop.apache.org/docs/r3.2.0/hadoop-project-dist/hadoop-common/core-default.xml
FileSystemモジュールを使うためには
<property>
<name>fs.foobar.impl</name>
<value>org.example.FooBarFileSystem</value>
</property>
$ tail META-INF/services/org.apache.hadoop.fs.FileSystem
org.example.FooBarFileSystem
もしくはjava.util.ServiceLoaderを使う
# 使ってなくてもロードされるのが難点
- 11. © 2019 NTT DATA Corporation 11
FileContext APIのためのもの
FileSystem実装を作るためのベースクラス、ではない
AbstractFileSystem?
<property>
<name>fs.s3a.impl</name>
<value>org.apache.hadoop.fs.s3a.S3AFileSystem</value>
</property>
<property>
<name>fs.AbstractFileSystem.s3a.impl</name>
<value>org.apache.hadoop.fs.s3a.S3A</value>
</property>
- 12. © 2019 NTT DATA Corporation 12
FileSystem APIをユーザ向けに整理する意図
意図通りに普及/移行しなかった...
ドキュメントがない
できることが(ほとんど)変わらない
Hadoopのコード自体が両方使っている
FileSystem実装を作るときにケアする必要あり
FileSystem実装をwrapするのが定番
see o.a.h.fs.DelegateToFileSystem
FileContext API (HADOOP-4952)
FileContext ctx = FileContext.getFileContext(uri);
FSDataInputStream is = ctx.create(path, ...);
ctx.setWorkingDir(path);
FSDataInputStream os = ctx.open(path, ...);
- 14. © 2019 NTT DATA Corporation 14
Linuxのコマンドと似たような雰囲気
CLI(FsShell)はJava APIを利用して作られたもの
CLIによるファイル操作
$ hadoop fs -mkdir -p /foo/bar
$ hadoop fs -chmod g+w /foo/bar
$ hadoop fs -ls -R /
drwxr-xr-x - iwasakims supergroup 0 2019-08-31 15:11 /foo
drwxrwxr-x - iwasakims supergroup 0 2019-08-31 15:11 /foo/bar
$ echo baz > baz.txt
$ hadoop fs -put baz.txt /foo/bar/
$ hadoop fs -head /foo/bar/baz.txt
baz
$ hadoop fs -rm -r /foo
- 15. © 2019 NTT DATA Corporation 15
URIに対応するインスタンスを取得
設定上のデフォルトFSなら明示的な指定は不要
FileSystemインスタンスの取得
scala> import org.apache.hadoop.conf.Configuration
scala> import org.apache.hadoop.fs.FileSystem
scala> import org.apache.hadoop.fs.Path
scala> val conf = new Configuration()
scala> conf.get("fs.defaultFS")
res0: String = hdfs://localhost:8020/
scala> val fs = FileSystem.get(conf)
scala> val path = new Path("hdfs://localhost:8020/")
scala> val fs = p.getFileSystem(conf)
scala> val fs = FileSystem.get(path.toUri(), conf)
- 16. © 2019 NTT DATA Corporation 16
ディレクトリの作成
基本的に親がなければ作成 (mkdir -p)
mkdirs
scala> val path = new Path("/foo/bar")
scala> fs.mkdirs(path)
res1: Boolean = true
scala> fs.exists(new Path("/foo"))
res2: Boolean = true
- 17. © 2019 NTT DATA Corporation 17
ファイル情報(FileStatus)の取得
listStatus
scala> val listing = fs.listStatus(new Path("/foo/bar"))
scala> val f = listing(0)
f: org.apache.hadoop.fs.FileStatus =
HdfsNamedFileStatus{path=hdfs://localhost:8020/foo/bar/baz.
txt; isDirectory=false; length=4; replication=1;
blocksize=134217728; modification_time=1567217427000;
access_time=1567152892141; owner=iwasakims; group=docker;
permission=rw-r--r--; isSymlink=false; hasAcl=false;
isEncrypted=false; isErasureCoded=false}
scala> fs.getFileBlockLocations(f.getPath(), 0, f.getLen())
res23: Array[org.apache.hadoop.fs.BlockLocation] =
Array(0,4,localhost)
- 18. © 2019 NTT DATA Corporation 18
ファイルの新規作成&書き込みオープン
得られたOutputStreamにバイト列を書き込む
先頭からシーケンシャルに
create
scala> import java.nio.charset.Charset
scala> val os = fs.create(new Path("/foo/bar/baz.txt"))
scala> val buf ="baz".getBytes(Charset.forName("UTF-8"))
buf: Array[Byte] = Array(98, 97, 122)
scala> os.write(buf, 0, buf.length)
scala> os.close()
- 19. © 2019 NTT DATA Corporation 19
ファイルの読み込みオープン
open
scala> import java.nio.ByteBuffer
scala> val is = fs.open(new Path("/foo/bar/baz.txt"))
scala> val buf = ByteBuffer.allocate(3).array
scala> is.read(buf, 0, buf.length)
res1: Int = 3
scala> new String(buf.array(), Charset.forName("UTF-8"))
res2: String = baz
scala> val bb = ByteBuffer.allocate(2)
scala> is.read(1, bb)
res3: Int = 2
scala> new String(bb.array(), Charset.forName("UTF-8"))
res4: String = az
- 20. © 2019 NTT DATA Corporation 20
入力ファイルを分割してタスクに対応づける
タスクごとにデータを処理する
作業用ディレクトリを作る
タスクの出力ファイルを作る
入力ファイルからレコードを読み出す
データを処理する
出力ファイルにレコードを書き込む
出力ファイルを出力先に移動する
フレームワークが入出力を抽象化
Hadoopジョブによるデータ処理の流れ
- 21. © 2019 NTT DATA Corporation 21
データ入力を抽象化するもの
入力をInputSplitに分割
レコードを読み出す
InputFormat
public abstract class InputFormat<K, V> {
public abstract
List<InputSplit> getSplits(JobContext context
) throws ...
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,
TaskAttemptContext context
) throws ...
}
- 22. © 2019 NTT DATA Corporation 22
ファイルからのデータを読み出しを抽象化
レコードを読み出すロジックは派生クラスが提供
TextInputFormat
SequenceFileInputFormat
AvroInputFormat
ParquetInputFormat
ユーザはパラメータを指定するだけ
どのクラスを使うか
処理対象ファイルのpath
...
FileInputFormat
- 23. © 2019 NTT DATA Corporation 23
データ出力を抽象化するもの
レコードを書き出す
出力できるかを確認する
出力を確定する
OutputFormat
public abstract class OutputFormat<K, V> {
public abstract RecordWriter<K, V>
getRecordWriter(TaskAttemptContext context
) throws ...
public abstract void checkOutputSpecs(JobContext context
) throws ...
public abstract
OutputCommitter getOutputCommitter(TaskAttemptContext context
) throws ...
}
- 25. © 2019 NTT DATA Corporation 25
マスターノード(NameNode)がボトルネック
1. NameNodeのヒープサイズ(<100GBくらい?)
2. 管理可能なスレーブノード数(<10000くらい?)
3. 処理可能なリクエスト数(<10万tpsくらい?)
ざっくりした目安
100万データブロックあたりヒープ1GB
ヒープサイズはGC的に100GB程度まで
1億ブロックで12.8PB (ブロックサイズ128MBで)
HDFSのスケーラビリティ
- 26. © 2019 NTT DATA Corporation 26
データをファイルという単位で管理
ファイルはデータ(バイト列)の入れ物
ディレクトリはファイルやディレクトリの入れ物
階層的なディレクトリ構造でファイルを整理
ファイル/ディレクトリはメタデータを持つ
所有者に応じたアクセス制御
...
ファイルシステム?
- 27. © 2019 NTT DATA Corporation 27
データをオブジェクトという単位で管理
オブジェクトはデータ(バイト列)の入れ物
バケットはオブジェクトの入れ物
フラットなキー空間でオブジェクトを管理
オブジェクトはメタデータを持つ
...
スケールアウトしやすい
REST APIによるアクセス(がメイン)
クラウドのマネージドサービスが利用可能
オブジェクトストレージ
- 28. © 2019 NTT DATA Corporation 28
S3AFileSystem
s3a://mybucket/path
ユーザが多く継続的に��良されている
Amazon S3
export HADOOP_OPTIONAL_TOOLS=hadoop-aws
classpathを通すのに設定が必要:
<property>
<name>fs.s3.impl</name>
<value>com.amazon.ws.emr.hadoop.fs.EmrFileSystem</value>
</property>
Amazon EMRでは利用されていない:
- 29. © 2019 NTT DATA Corporation 29
NativeAzureFileSystem
wasb://mycontainer@myaccount.blob.core.windows.net/path
Azureのオブジェクトストレージ
後継的なサービス(後述)が存在
Azure Blob Storage
export HADOOP_OPTIONAL_TOOLS=hadoop-azure
classpathを通すのに設定が必要:
<property>
<name>fs.defaultFS</name>
<value>wasb://mycluster@myaccount.blob.core.windows.net</value>
<final>true</final>
</property>
HDInsightでdefualtFSとして利用される:
- 30. © 2019 NTT DATA Corporation 30
AdlFileSystem
adl://mycontainer.azuredatalakestore.net/path
Azureのファイルシステム的ストレージサービス
後継的なサービス(後述)が存在
WebHDFS互換なREST APIを提供
Azure Data Lake Storage (Gen1)
export HADOOP_OPTIONAL_TOOLS=hadoop-azure-datalake
classpathを通すのに設定が必要:
- 31. © 2019 NTT DATA Corporation 31
AzureBlobFileSystem
abfs://mycontainer@myaccount.dfs.core.windows.net/path
Azureのストレージサービス
階層的名前空間(optional)を提供
Azure Data Lake Storage Gen2
export HADOOP_OPTIONAL_TOOLS=hadoop-azure
classpathを通すのに設定が必要:
<property>
<name>fs.defaultFS</name>
<value>abfs://mycluster@myaccount.dfs.core.windows.net</value>
<final>true</final>
</property>
HDInsightでdefualtFSとして利用される:
- 32. © 2019 NTT DATA Corporation 32
GoogleHadoopFileSystem
gs://container/path
サードパーティ実装
https://github.com/GoogleCloudPlatform/bigdata-interop
最近v2.0.0がリリース
https://cloud.google.com/blog/products/data-analytics/new-release-of-cloud-storage-connector-for-
hadoop-improving-performance-throughput-and-more
ランダムreadの性能向上(fadvise)
ディレクトリ操作のロック(cooperative locking)
Google Cloud Storage
- 34. © 2019 NTT DATA Corporation 34
FileSystem APIはHDFSの機能を抽象化したもの
全機能を備えているのはHDFS(DistributedFileSystem)
HDFSにない機能は定義されていない
例えばファイルのランダムupdate
オブジェクトストレージはファイルシステムではない
APIをwrapしてそれっぽく見せかける
あらまし
- 35. © 2019 NTT DATA Corporation 35
オブジェクトストレージには存在しない
末尾が/の空オブジェクトで模擬(されることが多い)
/dir1/
/dir1/file1.txt
/dir1/file2.txt
ディレクトリ削除: prefixが同じオブジェクトを全削除
abfs:
hierarchical namespace機能あり(optional)
有効化するとrenameとdeleteがO(1)
https://docs.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-namespace
ディレクトリ
- 36. © 2019 NTT DATA Corporation 36
キーの更新ができないデータストレージでは高価
新しい名前のオブジェクトにデータコピー
古い名前のオブジェクトを削除
ディレクトリのrenameが高価
prefixが同じオブジェクトをすべてrename
s3a: 実データのコピー&削除
abfs: メタデータのみの操作?
gs: メタデータのみコピー&削除
rename
- 37. © 2019 NTT DATA Corporation 37
s3a:
作成直後にリストに出ない可能性あり
削除直後にアクセスできる可能性あり
https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel
abfs:
hierarchical namespace enabledなら問題なし
gs: 問題なし?
https://cloud.google.com/storage/docsv/consistency
Eventual Consistency
- 38. © 2019 NTT DATA Corporation 38
S3AFileSystemの一貫性を改善するアドオン
補助的なメタデータ置き場としてDynamoDBを利用
create: S3にオブジェクト作成後DynamoDBに記録
listStatus: S3とDynamoDBの両方を見る
S3Guard (HADOOP-13345)
https://issues.apache.org/jira/secure/attachment/12821464/S3GuardImprovedConsistencyforS3AV2.pdf
- 39. © 2019 NTT DATA Corporation 39
タスク/ジョブ完了時に出力を確定
ユーザ/後続処理にすべての出力が見える
タスク/ジョブが失敗(中止)したら後片付け
ユーザ/後続処理にゴミが見えない
OutputCommitter
public abstract class OutputCommitter {
public abstract void setupJob(JobContext jobContext)
public void cleanupJob(JobContext jobContext)
public void commitJob(JobContext jobContext)
public void abortJob(JobContext jobContext, JobStatus.State state)
public abstract void setupTask(TaskAttemptContext taskContext)
public abstract boolean needsTaskCommit(TaskAttemptContext taskContext)
public abstract void commitTask(TaskAttemptContext taskContext)
public abstract void abortTask(TaskAttemptContext taskContext)
public boolean isRecoverySupported()
public boolean isCommitJobRepeatable(JobContext jobContext)
public boolean isRecoverySupported(JobContext jobContext)
public void recoverTask(TaskAttemptContext taskContext)
}
- 40. © 2019 NTT DATA Corporation 40
デフォルトのOutputCommitter
タスク成功時: 出力ファイルを出力ディレクトリに移動
ジョブ成功時: _SUCCESSという空ファイルを作成
失敗時: 出力ディレクトリをdelete
ファイルシステムを暗黙に想定
renameとdeleteが軽量でatomicでconsistent
see also ParquetOutputCommitter
https://github.com/apache/parquet-mr/blob/master/parquet-
hadoop/src/main/java/org/apache/parquet/hadoop/ParquetOutputCommitter.java
FileOutputCommitter
- 41. © 2019 NTT DATA Corporation 41
https://hadoop.apache.org/docs/r3.2.0/hadoop-aws/tools/hadoop-aws/committers.html
magic committer:
マルチパートアップロードを利用
各出力ファイルをジョブ完了時にまとめてcomplete
要S3Guard
S3A committers (HADOOP-13786)
<property>
<name>mapreduce.outputcommitter.factory.scheme.s3a</name>
<value>org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory</value>
</property>
<property>
<name>fs.s3a.committer.name</name>
<value>magic</value>
</property>
<property>
<name>fs.s3a.committer.magic.enabled</name>
<value>true</value>
</property>
- 42. © 2019 NTT DATA Corporation 42
一度書き込みcloseしたファイルへの追記
s3a, wasb: 不可
adl, abfs, gs: 可
append
scala> val p = new Path("s3a://mybucket/foo.txt")
scala> val fs = p.getFileSystem(conf)
scala> fs.append(p)
java.lang.UnsupportedOperationException: Append is not supported by S3AFileSystem
at org.apache.hadoop.fs.s3a.S3AFileSystem.append(S3AFileSystem.java:1139)
at org.apache.hadoop.fs.FileSystem.append(FileSystem.java:1397)
... 49 elided
- 43. © 2019 NTT DATA Corporation 43
未サポートのFSはsetPermissionがnoop
FileStatus#getPermissionはデフォルト値を返す
ファイルなら666
ディレクトリなら777
UnsupportedOperationExceptionは出ない
setOwner/getOwnerも同様
パーミッション
- 44. © 2019 NTT DATA Corporation 44
書き込み中のファイルの読み込み
hdfs, abfs: 可
open以後にwriteされたデータを読むには再open
gs: 可(オプショナル)
hsync/syncを呼ぶ必要あり
fs.gs.outputstream.type=SYNCABLE_COMPOSITE
s3a: 不可
Tailing
- 45. © 2019 NTT DATA Corporation 45
FSDataOutputStreamの機能
hflushとhsyncが使えるか
(fsyncに相当するAPI)
未サポートだとflushが呼ばれる
Syncable
@Override // Syncable
public void hflush() throws IOException {
if (wrappedStream instanceof Syncable) {
((Syncable)wrappedStream).hflush();
} else {
wrappedStream.flush();
}
}
- 46. © 2019 NTT DATA Corporation 46
FSDataInputStream#read(ByteByffer buf)の有無
see also ByteBufferPositionedReadable
ByteBufferReadable
scala> val p = new Path("s3a://mybucket/foo.txt")
scala> val fs = p.getFileSystem(new Configuration())
scala> val is = fs.open(p)
scala> val buf = ByteBuffer.allocate(3).array
scala> is.read(buf, 0, buf.length)
res13: Int = 3
scala> new String(buf, Charset.forName("UTF-8"))
res14: String = foo
scala> is.seek(0)
scala> val bb = ByteBuffer.allocateDirect(3)
scala> is.read(bb)
java.lang.UnsupportedOperationException: Byte-buffer read unsupported by input stream
at org.apache.hadoop.fs.FSDataInputStream.read(FSDataInputStream.java:152)
... 49 elided
- 47. © 2019 NTT DATA Corporation 47
hasCapabilityメソッドで確認できる
まだ未対応の実装も多いはず..
StreamCapabilities
public boolean hasCapability(String capability) {
switch (StringUtils.toLowerCase(capability)) {
case StreamCapabilities.READAHEAD:
case StreamCapabilities.DROPBEHIND:
case StreamCapabilities.UNBUFFER:
case StreamCapabilities.READBYTEBUFFER:
case StreamCapabilities.PREADBYTEBUFFER:
return true;
default:
return false;
}
}
- 49. © 2019 NTT DATA Corporation 49
新作オブジェクトストレージ
/volume/bucket/keyという名前空間
S3互換の機能も提供
REST APIと認証(Signature Version 4)
Hadoopのサブモジュール
設定、RPC、認証、暗号化などの共通パーツを利用
depending on hadoop-common
リリースはHadoop本体とは独立
Ozoneのbinary tarballはOzone専用
Hadoopのbinary tarballにはOzoneは含まれない
Ozoneの概要
- 50. © 2019 NTT DATA Corporation 50
Hadoop Commonのモジュール利用している
HDFSのモジュールを(ほとんど)利用していない
大部分は新規コード
OzoneとHDFSとの関係
$ ozone --daemon start datanode
<property>
<name>dfs.datanode.plugins</name>
<value>org.apache.hadoop.ozone.HddsDatanodeService</value>
</property>
DataNode内で追加のモジュールとしても起動可能
Ozone専用のプロセスを起動する場合:
- 51. © 2019 NTT DATA Corporation 51
hadoop-hddsとhadoop-ozone
HDDS: 分散KVS (using RocksDB and Ratis)
Ozone: 名前空間管理 (using RocksDB and Ratis)
HDDS? Ozone?
https://github.com/apache/hadoop/blob/trunk/hadoop-hdds/docs/static/OzoneOverview.svg
- 52. © 2019 NTT DATA Corporation 52
FSNameSystem:
ファイルシステムメタデータ
ファイルとブロックの対応を管理
全体をメモリ上に置く必要あり
BlockManager:
ブロックとDataNodeとの対応を管理
FSNameSystemと密結合
分離したいができていない
NameNodeのFSNameSystemとBlockManager
- 53. © 2019 NTT DATA Corporation 53
OzoneにアクセスするためのFileSystem実装
o3fs://bucket.volume.omhost:9862/path
RPCでOzoneにアクセス
Eventually Consistentではない
ディレクトリは空オブジェクト(末尾/)で表現
renameはOzoneManagerのメタデータのみの更新
ディレクトリのrename/deleteは非atomic
append不可
OzoneFileSystem
- 55. © 2019 NTT DATA Corporation 55
Hadoopはデータストアへの透過的なアクセスを提供
とはいえデータストアごとの違いに注意
実装ごとのチューニングノブも多数
その辺すこし気にしつつご活用ください
まとめ
- 56. © 2019 NTT DATA Corporation本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です。