TT Lab
はじめる
学ぶ 学習パス コース

Apache Hadoop — 1つのPodにHDFSとYARNを立てて運用する

小さなファイルはディスクではなく NameNode のメモリを食う

TT Labで続きを見る

一言でいうと

HDFSでファイル1つのコストは、バイトではなくNameNodeメモリの中のオブジェクト数で数えられます。1KBのファイル2,000個は、ディスクでは2MBですが、NameNodeにとっては、ファイル2,000個とブロック2,000個のオブジェクトです。Hadoop Archive(HAR)は、そのオブジェクトを数個にまとめて名前空間を軽くし、元のファイルを削除するのは人間の仕事です。

なぜこれが問題になるのか

HDFS設計ドキュメントは、NameNodeが名前空間全体とブロックの対応表をメモリに持っていると書いています。ディスクは、DataNodeを増やせばいくらでも増えますが、名前空間は、NameNodeの1プロセスのヒープの中にあります。ファイル1つ、ディレクトリ1つ、ブロック1つが、すべてそのヒープの中のオブジェクトです。そのため、HDFSの容量には、2つの軸があります。バイトで測るディスク容量と、オブジェクト数で測る名前空間の容量です。

同じドキュメントは、HDFSが大きなファイルに合わせてあることを、はっきりさせています。典型的なファイルはギガバイトからテラバイトで、1つのインスタンスが、数千万個のファイルをサポートする必要があると書いています。数千万という規模が、設計が念頭に置いたサイズです。同じ数千万個でも、ファイルが平均1GBなら数十ペタバイトを収められますが、平均10KBなら、数百ギガバイトを収める間に、名前空間が先にいっぱいになります。Federationドキュメントが、NameNodeを複数置く理由として「小さなファイルが多いデプロイ」を直接挙げているのも、このためです。

小さなファイルは、計算側でも損です。MapReduceチュートリアルによると、マップ数は入力のブロック数が左右し、タスクを準備するのに時間がかかるため、マップ1つが少なくとも1分は動くのが望ましいです。1KBのファイル2,000個は、ブロック2,000個で、準備に数秒ずつかかるマップタスクが、1秒もかからない仕事のために数千回起動します。

どう動くのか

オブジェクト数は、数値で見られます。メトリクスドキュメントのFSNamesystemの項目で、FilesTotalはファイルとディレクトリの数、BlocksTotalは割り当てられたブロックの数です。NameNodeのWebアドレス(デフォルトのポート9870)の/jmxで、直接読めます。空のディレクトリ1つに小さなファイル2,000個をアップロードすると、2つの値は、このように動きます(前の数字は例です)。

put 전     FilesTotal=  41   BlocksTotal=  12
put 2,000  FilesTotal=2042   BlocksTotal=2012   <- 파일 2,000 + 디렉터리 1, 블록 2,000

小さなファイル1つは、ブロック1つを作ります。hdfs-default.xmlのdfs.blocksizeのデフォルトは128MBですが、それはブロックの最大サイズにすぎません。1KBのファイルのブロックは、DataNodeのディスクで1KB強しか占めません。ディスクは無駄になりません。無駄になるのは、そのブロック1つを記憶するNameNodeのオブジェクトと、そのブロックを報告・管理する仕事です。小さなファイル問題を「ディスクがもったいない」と説明すると、間違った説明になります。

Hadoop Archive(HAR)は、これらのオブジェクトを折りたたみます。Hadoop Archivesの案内によると、.harはファイルシステム上の1つのディレクトリで、中に、メタデータの_index・_masterindexと、データファイルのpart-*が入っています。元のファイルの内容は、いくつかのpartファイルに連結され、_indexが、各元ファイルの名前とpartファイルの中の位置を記憶します。2,000個のファイルが、NameNodeにとっては、数個のファイルになるのです。

hadoop archive -archiveName logs.har -p /data/raw day1 /data/archive
hdfs dfs -ls har:///data/archive/logs.har/day1

HARは、ファイルシステムの層として自分を現します。har://アドレスで、ls・catのようなシェルコマンドがそのまま使え、MapReduceの入力としても使えます。その代わり、いくつか代償があります。

HARが減らすのは、名前空間のオブジェクト数です。HARをMapReduceの入力に入れても、中の論理ファイルは、やはりファイルとして見えるので、マップ数の問題まで自動的に解決するわけではありません。計算が問題なら、最初から大きなファイルにまとめて書くほうがよいです。

distcpで移して、展開する

大量のコピーは、DistCpで行います。これもMapReduceジョブです。コピーするファイルの一覧を展開して、マップタスクに分け、マップごとに自分の担当分をコピーします。ドキュメントによると、マップごとに同じくらいのバイト数を担当するよう分けますが、ファイルが最小の単位なので、小さなファイルが多いと、コピーもファイル数に引きずられます。-updateは、コピー先にないか、違うファイルだけをコピーするので、2回目の実行から速くなります。HARを展開するのもコピーです。har://アドレスをコピー元にhdfs dfs -cpすれば順番に、distcpすれば並列に展開されます。

現場での姿

1つ目は、NameNodeがGCで止まることです。ディスクは半分空いているのに、NameNodeのヒープがいっぱいになって、長いGCが繰り返されます。原因をたどると、誰かが5分ごとに数千個ずつ小さなファイルを投入する収集ツールです。FilesTotalの推移を、監視項目に入れる必要がある理由です。

2つ目は、ディレクトリ1つにファイルが多すぎることです。dfs.namenode.fs-limits.max-directory-itemsのデフォルトは1,048,576です。1つのディレクトリに投入し続ける収集ツールは、いつかこの壁にぶつかって、書き込みが失敗します。

3つ目は、アーカイブを作ったのに、オブジェクトが減らないことです。元のファイルを削除していないのです。アーカイブをharアドレスで読んで確認し、そのあとに、元のファイルを削除します。順序を逆にすると、元に戻す道がありません。

4つ目は、本当の解決は、書く側にあることです。HARは、すでに溜まったものを片付けるツールです。新しく溜まるものは、収集の段階でまとめて書くか、定期的に大きなファイルにまとめる作業を置かなければ、問題が再び育ちます。

実務で本当に大切なこと

次のラボですること

小さなファイル2,000個をHDFSにアップロードして、countとfsckで、ファイル・ディレクトリ・ブロックの数を数え、NameNodeが抱えたオブジェクト数を測ります。YARNを起動せずにローカルモードでhadoop archiveを実行して、そのファイルをレプリケーション1のHAR 1つにまとめ、harアドレスで一覧を見て中のファイル数を数えたあと、ファイル1つを読んでローカルに取り出します。元のディレクトリとHARのオブジェクト数を並べて比べ、どれだけ減るかを数値で確認し、最後にdistcpで、アーカイブのバックアップコピーを作ります。