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

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

スペースクォータは書く前にブロック一つ分を確保する

TT Labで続きを見る

一言でいうと

HDFSのクォータは2つあります。名前クォータは、ディレクトリの下のファイルとディレクトリの数を、スペースクォータは、レプリカまで数えたバイト数を制限します。スペースクォータは、すでに書いた量ではなく新しいブロック1つを最後まで埋める分を見て制限するので、1バイトのファイルが30MBのクォータに引っかかることが起こります。

なぜクォータが必要なのか

共有クラスターには、2種類の事故があります。あるチームのジョブが暴走してディスクをいっぱいにする事故と、小さなファイルが数百万個あふれてNameNodeが耐えられなくなる事故です。前者はバイトの問題で、後者は数の問題です。ファイル1つ、ディレクトリ1つが、どちらもNameNodeメモリのオブジェクトであることは、前のモジュールで見ました。ディスクがいくら余裕でも、名前が多すぎると、NameNodeから先に倒れます。

そのため、HDFSクォータ管理ガイドは、2つのクォータを別々に用意しています。両者は独立して動作しますが、管理方式と実装は並んでいます。クォータはディレクトリにかかり、そのディレクトリを根とするツリー全体に適用されます。設定と解除は、管理者だけができます。

どう動くのか

まず名前クォータです。名前クォータは、ツリー内のファイル名とディレクトリ名の数に対するハード上限です。超えると、ファイルやディレクトリの作成が失敗します。ガイドで注目すべき文があります。クォータを1にかけると、そのディレクトリは空でいるしかなく、カッコの中に、ディレクトリは自分自身もクォータに数えると書かれています。そのため、クォータ10のディレクトリには、ファイルを9個までしか入れられません。

いくつか性質があります。クォータは、名前を変えてもディレクトリに付いていき、名前の変更がクォータを超えさせるなら、その名前の変更が失敗します。すでに超えた状態のディレクトリにも、クォータの設定自体は成功します。新しく作ったディレクトリには、クォータがありません。超えたときにクライアントが受け取る例外は、NSQuotaExceededExceptionです。

スペースクォータは事前に確保する

スペースクォータは、ツリー内のファイルが使うバイト数に対する上限で、ブロックのレプリカ1つ1つがクォータに入ります。ガイドの例のとおり、レプリケーション係数3の1GBのデータは、クォータ3GBを消費します。ディレクトリ自体は容量を使わず、メタデータが使う容量も数えません。レプリケーション係数を変えると、クォータがその分増減します。

核心は、この文です。クォータがブロック1つを満杯に書く余裕を許さなければ、ブロックの割り当てが失敗します。NameNodeは、ファイルが実際に何バイト書くかを事前に知ることができません。ブロックを渡したあと、クライアントがそのブロックを最後まで埋めることもあります。そのため、新しいブロックを渡す瞬間に、「ブロックサイズ×レプリケーション係数」の分が残っているかを見ます。

なぜこんなに保守的に確保するのでしょうか。ブロックは、パイプラインに沿って流れている間は、最終サイズが決まりません。書き込みの途中でクォータを超えたからと止めると、書きかけのブロックと作りかけのファイルが残ります。ブロックを渡す前に、最悪の場合を確認しておけば、拒否は常に、バイトが流れる前、ブロックの境界で起こります。その代わり、払う代償があります。小さなファイルも大きな余裕を要求し、複数のファイルを同時に書くと、開いているブロックごとに、その余裕が別々に必要です。残りのクォータが十分に見えても、同時に書く作業が多いと、突然ブロックされる理由です。

クォータ30MBのディレクトリに1バイトのファイルを書く2つの場合を示す図です。ブロック128MBでレプリケーション1なら、NameNodeがブロックを渡す前に128MBが残っていることを要求するため、30MBのクォータ線を超えて拒否されます。ブロックを4MBに減らすと、必要な分が4MBなので通り、閉じたあとは、実際に書いた1バイトだけが使用量として残ります

hdfs-default.xmlのdfs.blocksizeは、デフォルトが134217728バイト(128MB)です。レプリケーション係数が1でも、30MBのクォータのディレクトリには、128MB分の余裕がないので、内容が1バイトだけのファイルも、最初のブロックを受け取れず、DSQuotaExceededExceptionで失敗します。このファイルだけブロックサイズを4MBに減らすと、必要な余裕が4MBになって通ります。ファイルを閉じたあとにクォータに残る使用量は、実際に書いたバイト数にレプリケーション係数を掛けた値です。事前に確保するのは、書き込んでいる間だけです。

hdfs dfsadmin -setSpaceQuota 30m /team/etl
hdfs dfs -put one-byte.txt /team/etl/                         # DSQuotaExceededException
hdfs dfs -D dfs.blocksize=4m -put one-byte.txt /team/etl/     # 통과
hadoop fs -count -q -v /team/etl

ブロックサイズを減らすことにも、下限があります。dfs.namenode.fs-limits.min-block-sizeがデフォルト1MBなので、それより小さくはできません。極端な場合も、ガイドにあります。スペースクォータ0は、ファイルの作成は許しますが、そのファイルにブロックは1つも付けられません。空のファイルだけ作れるという意味です。

クォータを読む方法

ファイルシステムシェルのcount -qは、8つの列を出力します。順序は、QUOTA、REMAINING_QUOTA、SPACE_QUOTA、REMAINING_SPACE_QUOTA、DIR_COUNT、FILE_COUNT、CONTENT_SIZE、PATHNAMEです。クォータがなければ、クォータの列はnone、残りの列はinfで出ます。ここでCONTENT_SIZEはレプリケーション前のサイズで、残りのスペースクォータは、レプリカまで数えた値から減ります。2つの数値を混ぜて計算すると、レプリケーション係数の分だけずれます。-hを付けると人間が読みやすい単位で、-vを付けるとヘッダー行とともに出ます。

クォータはfsimageに保存されるので、再起動しても残り、設定したり削除したりするたびに、エディットログに記録されます。解除は-clrQuotaと-clrSpaceQuotaです。ストレージの種類(SSD・DISKなど)ごとに別にかけるクォータもありますが、ストレージポリシーを使うディレクトリでだけ意味があります。

現場での姿

1つ目は、「クォータがたっぷり残っているのに、ファイル1つ書けない」ことです。残りのスペースクォータが、ブロックサイズ×レプリケーション係数より小さければ、そうなります。128MBブロックでレプリケーション3なら、残りのクォータが384MB未満になった瞬間から、新しいブロックを受け取れません。クォータを決めるときは、この余裕を計算に入れます。

2つ目は、小さなファイルがたくさんあるチームには、名前クォータのほうが急務であることです。スペースクォータだけをかけると、1KBのファイル100万個が問題なく入ってきます。NameNodeを守るのは、名前クォータです。

3つ目は、ごみ箱もクォータを消費することです。設計ドキュメントのとおり、ユーザーごとのごみ箱はホームディレクトリの下の.Trashにあるので、ホームディレクトリにクォータをかけると、削除したファイルがごみ箱にある間、ずっとそのクォータを使います。シェルのドキュメントが、-skipTrashを、クォータを超えたディレクトリでファイルを削除する必要があるときに役立つと説明している理由です。

4つ目は、クォータは名前の変更もブロックすることです。大きなディレクトリを、クォータがかかった場所に移動しようとして失敗するのは、正常な動作です。

5つ目は、削除したのに数値がそのままであることです。シェルのドキュメントによると、countは-xを指定しない限り、そのパスの下のスナップショットに残った項目まで、すべて数えて結果を出します。スナップショットが保持しているファイルは、現在のツリーから削除しても、集計からすぐには外れません。スナップショットは、次のモジュールで扱います。

実務で本当に大切なこと

次のラボですること

ディレクトリに名前クォータ10をかけて、小さなファイルを1つずつアップロードし、何個目でブロックされるかを数えて、ディレクトリ自身が1つ分を消費することを確認します。続いて、スペースクォータ30MBをかけて、1バイトのファイルがデフォルトのブロックサイズの予約のために拒否されるエラーを再現したあと、同じファイルをブロックサイズ1MiBでアップロードして、入ることを見ます。count -qの列を読んで、2つのディレクトリの残りのクォータを解釈し、クォータがないディレクトリのファイルを、すでにいっぱいのディレクトリに移動しようとして、名前の変更まで拒否されることを確認します。レポートには、実際に入ったファイル数と、1バイトのファイルが予約しようとしたバイト数を、数値で書きます。