Apache Hadoop — 1つのPodにHDFSとYARNを立てて運用する
小さなファイル 2,000 個をアーカイブにまとめ、NameNode のオブジェクト数を測る
目標
センサーCSV 2,000個(ファイル1つに数百バイト)をHDFSにアップロードして、NameNodeが持たなければならないオブジェクト(ファイル・ディレクトリ・ブロック)がいくつあるかを測り、Hadoop Archive(HAR)でまとめて、その数がどれだけ減るかを見ます。まとめたあとも、har://で元のファイルを1つ1つ読めることと、distcpでアーカイブを丸ごと移すことまで試します。
なぜ重要なのか
NameNodeは、ファイル・ディレクトリ・ブロック1つ1つを、Javaオブジェクトとしてヒープに持っています。ファイルが1バイトでも1GBでも、オブジェクト数は同じです。そのため、HDFSの限界は、たいていディスクではなくNameNodeのヒープで、数百万個の小さなファイルは、ディスクをほとんど使わずに、NameNodeを先に倒します。MapReduce・Sparkの立場でも、小さなファイルは、ファイルごとにタスクやオープンのコストが付くので、遅くなります。
解決策は、ファイルを少なく作ることです。書くときにまとめて書くか(Sparkのrepartition・coalesce、大きなコンテナファイル形式)、すでにできたものをまとめます。HARは後者の方法です。多くのファイルを、大きなファイル数個(part-*)とインデックス(_index・_masterindex)に変えて、NameNodeのオブジェクト数を減らし、読むときは、har://ファイルシステムがインデックスを見て、元のパスで見つけてくれます。その代わり、一度作ったアーカイブは変更できません(読み取り専用)。
このラボは、アーカイブとdistcpを、YARNなしでローカル実行で動かします。どちらもMapReduceジョブですが、実行場所は設定1つで変わります。同じジョブが、ノートPCでもクラスターでも動きます。
ステップ
/data/smallディレクトリ(ファイル2,000個)をアップロードしてください(アップロード先: /user/root/small/raw、hdfs dfs -put -t 8で複数スレッドでアップロードすると速いです)。- rawのファイル数・ディレクトリ数・ブロック数を、/root/hdp/small/objects.jsonに
{"raw_files": 정수, "raw_dirs": 정수, "raw_blocks": 정수}の形式で書き込んでください(プレースホルダーは順に整数、整数、整数です)。 hadoop archive -D mapreduce.framework.name=local -archiveName raw.har -p /user/root/small -r 1 raw /user/root/small/archiveで、アーカイブを作成してください。hdfs dfs -ls -R har:///user/root/small/archive/raw.harで、アーカイブの中のファイルの数を数えて、/root/hdp/small/har_files.txtに整数で記入してください。har://パスでraw/sensor-0042.csvを読んで、ローカルに保存してください(保存先: /root/hdp/small/sensor-0042.csv)。- rawとraw.harのNameNodeオブジェクト数(ファイル数とディレクトリ数とブロック数の合計)を、/root/hdp/small/compare.jsonに
{"raw_objects": 정수, "har_objects": 정수}の形式で書き込んでください(プレースホルダーは順に整数、整数です)。 hadoop distcp -D mapreduce.framework.name=local /user/root/small/archive /user/root/backup/small-archiveで、アーカイブをコピーしてください。- /root/hdp/small/report.mdに、
## NameNode 가 치르는 값・## HAR・## 옮기기の3つの節を書いてください(最初と3つ目の見出しは韓国語で、順に「NameNodeが支払う代償」「移動」を意味します)。最初の節にステップ6のraw_objectsを、2つ目の節にhar_objectsを入れてください。
参考
- ファイル・ディレクトリ数:
hdfs dfs -count <경로>(またはWebHDFSのGETCONTENTSUMMARY)、ブロック数:hdfs fsck <경로>のサマリーのTotal blocks (validated)です(プレースホルダーはパスです)。 - HARの中:
_index(ファイルごとに1行。パス、種類、どのpartのどこから何バイトか)、_masterindex(インデックスのインデックス)、part-0(内容を連結したもの)。hdfs dfs -cat /user/root/small/archive/raw.har/_index | headで見てください。 -r 1は、アーカイブファイルのレプリケーション係数です(デフォルトは3で、DataNodeが1つなのでレプリケーション不足になります)。- よくあるミスは、
har://パスにアーカイブ名を抜くこと、-pで指定した親を基準にした相対パス(raw)を、絶対パスで書くこと、ディレクトリの行までファイル数に入れることです。 - 公式ドキュメント: Hadoop Archives Guide・DistCp Guide・HDFS Architecture・FileSystem Shell — count
小さなファイル2,000個をアップロードする
/data/smallをHDFSにアップロードしてください(アップロード先: /user/root/small/raw)。hdfs dfs -put -t 8 /data/small /user/root/small/rawのように複数スレッドでアップロードすると速いです。
1スレッドでアップロードすると、ファイルごとにNameNodeと何度もやり取りするので、2,000個に10分以上かかります。小さなファイルは、サイズではなく個数でコストを払います。これこそが小さなファイル問題です。
NameNodeが持っているオブジェクトを数える
hdfs dfs -countとhdfs fsckで、/user/root/small/rawのファイル数・ディレクトリ数・ブロック数を求めて、/root/hdp/small/objects.jsonに{"raw_files": 정수, "raw_dirs": 정수, "raw_blocks": 정수}の形式で書き込んでください(プレースホルダーは順に整数、整数、整数です)。
ファイル1つにブロック1つ(ファイルがブロックサイズより小さいので)、それにディレクトリ1つ。NameNodeヒープでは、この3つがすべてオブジェクトです。ブロックサイズが128MBでも、数百バイトのファイルのブロックが128MBを占めるわけではありませんが、オブジェクトは同じく1つを占めます。
アーカイブにまとめる
hadoop archive -D mapreduce.framework.name=local -archiveName raw.har -p /user/root/small -r 1 raw /user/root/small/archiveで、/user/root/small/archive/raw.harを作成してください。
アーカイブはMapReduceジョブです。入力ファイルの一覧を分けて、内容をpart-*に連結し、ファイルごとにどこに入ったかを_indexに書きます。-D mapreduce.framework.name=localは、YARNなしで、今のJVMの中で動かすという意味です。元のファイル(raw)はそのまま残ります。
har://で中を見る
hdfs dfs -ls -R har:///user/root/small/archive/raw.harの出力から、ファイル(権限が-で始まる行)の数を数えて、/root/hdp/small/har_files.txtに整数で記入してください。
har://ファイルシステムは、_indexを読んで、元のディレクトリツリーを見せます。NameNodeは、この2,000個を知りません。アーカイブの中のファイルは、NameNodeのオブジェクトではなく、インデックスの行です。
アーカイブの中のファイルを1つ読む
hdfs dfs -cat har:///user/root/small/archive/raw.har/raw/sensor-0042.csvで読んだ内容を、ローカルに保存してください(保存先: /root/hdp/small/sensor-0042.csv)。
パスはhar://<아카이브 경로>/<-p 기준 상대 경로>です(プレースホルダーは順にアーカイブのパス、pオプションで指定した親を基準とした相対パスです)。harファイルシステムは、_masterindex・_indexでpartファイルの中の位置を探し、そのバイトだけを読みます。元の/data/small/sensor-0042.csvと、cmpで比べてみてください。
オブジェクト数を比べる
rawとraw.harそれぞれのNameNodeオブジェクト数(ファイル数とディレクトリ数とブロック数の合計)を求めて、/root/hdp/small/compare.jsonに{"raw_objects": 정수, "har_objects": 정수}の形式で書き込んでください(プレースホルダーは順に整数、整数です)。
rawは、ファイル2,000とディレクトリ1とブロック2,000です。raw.harは、ディレクトリ1つと、ファイル数個(_index・_masterindex・part-0・_SUCCESS)と、そのブロックだけです。何分の1に減ったかを計算してみてください。
distcpで丸ごと移す
hadoop distcp -D mapreduce.framework.name=local /user/root/small/archive /user/root/backup/small-archiveで、アーカイブをコピーしてください。コピーのraw.harの中のファイルが、元と同じ長さである必要があります。
distcpは、ファイルの一覧を分けて、複数のマップが並列にコピーするMapReduceジョブです。クラスター間の移行やバックアップに使う標準ツールで、小さなファイルをまとめておけば、コピーするファイル数が減って、これも速くなります。コピー先のパスがなければ、元のディレクトリの内容が、その名前でコピーされます。
小さなファイルの代償を数値で
/root/hdp/small/report.mdに、## NameNode 가 치르는 값・## HAR・## 옮기기の3つの節を書いてください(最初と3つ目の見出しは韓国語で、順に「NameNodeが支払う代償」「移動」を意味します)。最初の節にステップ6のraw_objectsを、2つ目の節にhar_objectsを、数値で入れてください。
NameNodeヒープが何を数えるか、HARが何を減らして何をあきらめるか(読み取り専用)、小さなファイルが最初からできないようにするには、書く側で何をすべきかを書いてください。