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

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

ファイルはブロックに分かれて DataNode のディスクに置かれる

TT Labで続きを見る

一言でいうと

HDFSのファイルは、同じサイズのブロックに分割されてDataNodeのディスクに普通のファイルとして置かれ、ブロックごとにレプリケーション係数の数だけコピーが作られます。ブロックサイズはファイルごとに決められますが、一度決めると、ブロック数、NameNodeの負担、さらにはファイルチェックサムの値まで、それに従って変わります。

なぜブロックはこんなに大きいのか

ローカルファイルシステムのブロックは、通常数KBです。HDFSのブロックは、hdfs-default.xmlのdfs.blocksizeでデフォルト134217728バイト、つまり128MBです。数万倍の差があるのには、理由があります。

1つ目に、HDFSは大きなファイルを最初から最後まで読む仕事に合わせて設計されています。設計ドキュメントは、レイテンシよりスループットを重視すると書いています。ブロックが大きければ、一度場所を確保したあと、長く順次に読みます。2つ目に、ブロック1つ1つがNameNodeメモリのオブジェクトです。1TBのファイルを4KBのブロックに切ると、ブロックが2億個をはるかに超え、NameNodeはそのすべての位置をメモリに持つ必要があります。3つ目に、ブロックは計算を分ける単位としても使われます。設計ドキュメントの表現のとおり、データを移動するより計算を移動するほうが安いので、MapReduceのようなエンジンは、ブロックがある場所で処理を実行します。

だからといって、無限に小さくすることもできません。dfs.namenode.fs-limits.min-block-sizeは、デフォルト1048576バイト(1MB)で、説明には、誤って非常に小さなブロックサイズを設定してブロックが急増するのを防ぐための下限だと書かれています。

どう動くのか

ブロックサイズはファイルごとに違います: 設計ドキュメントは、ブロックサイズとレプリケーション係数をファイル単位で決められると書いています。クラスターのデフォルト値は、作成時に別に指定しなかったファイルに使われるだけです。また、最後のブロックを除くすべてのブロックは同じサイズです。そのため、12MiBのファイルをブロック4MiBでアップロードするとブロック3個、デフォルトの128MBでアップロードするとブロック1個になります。1KBのファイルもブロック1つを占めます。小さなファイル50個は、ブロック50個です。ブロックは、ファイル同士で分け合いません。

# 이 파일만 블록 4MiB 로 올린다
hdfs dfs -D dfs.blocksize=4194304 -put big.dat /data/big.dat
# 블록 목록과 위치를 본다
hdfs fsck /data/big.dat -files -blocks -locations

HDFSコマンドのドキュメントのfsckは、-files -blocks -locationsで、ファイルごとにブロックIDと、そのブロックを持つDataNodeを表示します。ここでブロックIDが得られれば、次の段階に下りていけます。

ディスク上のブロック: DataNodeがブロックを置く場所はdfs.datanode.data.dirで、デフォルトはfile://${hadoop.tmp.dir}/dfs/dataです(ラボのイメージは/var/lib/hadoop/dataに移してあります)。その下をたどると、blk_で始まる普通のファイルがあり、隣に同じ名前に.metaが付いた対のファイルがあります。前者がブロックのバイトそのままで、後者がそのバイトのチェックサムです。設計ドキュメントは、DataNodeが1つのディレクトリにファイルを集めず、サブディレクトリを自動的に分けると説明しています。ローカルファイルシステムが、1つのディレクトリにある膨大な数のファイルをうまく扱えないからです。テキストファイルをアップロードしたなら、blk_ファイルをheadで開いて、元の内容をそのまま読めます。HDFSが特別な保存形式を使っていないという意味です。

レプリケーション: レプリケーション係数のデフォルトはdfs.replicationの3です。レプリケーション係数が3のときの配置ポリシーを、設計ドキュメントは次のように説明しています。1つは書き込み側のマシン(または同じラックの任意のノード)に、1つは別のラックのノードに、最後は、その別のラックのさらに別のノードに置きます。ラック1つが丸ごと落ちても生き残り、ラック間の書き込みトラフィックは減らします。

重要な制約が1つあります。NameNodeは、1つのDataNodeに同じブロックのレプリカを2つ以上置かないので、作れるレプリカの最大数は、そのときのDataNode数です。DataNodeが1台だけのラボ環境で、ファイルシステムシェルのsetrep 3をかけると、コマンドは成功しますが、コピーはいつまでも1つのままです。fsckは、このようなブロックをレプリケーション不足(under-replicated)として報告します。コマンドが成功したからといって、複製されたわけではありません。

チェックサムがブロックサイズに結び付く理由

HDFSは、バイトを小さな断片ごとに検査します。dfs.bytes-per-checksumは512、dfs.checksum.typeはCRC32Cがデフォルトです。512バイトごとにCRCを1つ計算して、.metaファイルに置きます。

問題は、hadoop fs -checksumが返すファイルレベルのチェックサムです。デフォルトの組み合わせ方式dfs.checksum.combine.modeはMD5MD5CRCです。名前のとおり、512バイトごとのCRCをブロックごとにMD5でまとめ、そのブロックのMD5をさらにMD5でまとめます。ブロックの境界が計算の過程に入っているので、内容が同じでもブロックサイズが違えば値が変わります。hdfs-default.xmlの説明も、元の方式はブロック配置が違うファイル同士を比較できず、COMPOSITE_CRCのような方式は、ブロック配置に関係なく比較できると書いています。

hadoop fs -checksum /a/4m.dat /a/128m.dat        # 값이 다르다
hadoop fs -D dfs.checksum.combine.mode=COMPOSITE_CRC \
          -checksum /a/4m.dat /a/128m.dat        # 값이 같다

これが現場で事故になる場所が、コピーの検証です。DistCpドキュメントは、-updateが、コピー元とコピー先のサイズ・ブロックサイズ・チェックサムを比べると書いています。ブロックサイズを保持せずに移したコピーは、デフォルトの方式では、チェックサムが食い違って見えることがあります。

現場での姿

1つ目は、「レプリケーション3に変えたのに、fsckが警告し続ける」ことです。DataNodeが3台より少ないか、ラックの配置を満たせる場所がないときに起きます。setrepは目標を変えるだけで、実際にコピーを作るのは、NameNodeがDataNodeに指示する裏側の仕事です。

2つ目は、ブロックサイズを小さくすると、NameNodeが先に苦しくなることです。ブロック数は、ファイルサイズをブロックサイズで割った数なので、ブロックサイズを半分にすればブロックオブジェクトが2倍になります。小さなファイルが多いのと同じ方向の負担です。

3つ目は、コピーしたファイルのチェックサムが違うからといって、データが壊れたわけではないことです。ブロックサイズが違うせいかもしれません。組み合わせ方式をCOMPOSITE_CRCに変えて比べ直せば、見分けられます。

4つ目は、DataNodeのディスクのblk_ファイルを手で触らないことです。そのファイルは、NameNodeのブロックの地図と対になっています。移動したり削除したりすると、NameNodeはブロックレポートであとから気づき、その間の読み取りが失敗します。

実務で本当に大切なこと

次のラボですること

12MiBのファイルをブロック4MiBでアップロードし、ブロック3つに分割されるのをfsckで見て、そのブロックIDをたどってDataNodeのローカルディスクから実際のブロックファイルを見つけて開きます。同じファイルをデフォルトのブロックサイズでアップロードすると、ブロックが1つになることも比べます。レプリケーション係数を3に上げて、DataNodeが1台だけの環境でレプリケーション不足がどう報告されるかを見て、小さなファイル複数が、その数だけのブロックになることを数えます。最後に、ブロックサイズだけが違う2つのファイルのチェックサムが、デフォルトの方式では異なり、COMPOSITE_CRCでは同じになることを確認します。