はじめに

このドキュメントでは、Hadoopファイルシステムの実装者と保守者、およびHadoop FileSystem APIのユーザーのために、Hadoop互換ファイルシステムの必須動作を定義します。

Hadoop操作のほとんどは、HadoopテストスイートでHDFSに対してテストされ、最初は`MiniDFSCluster`を介して、リリース前にベンダー固有の「本番」テストによって、そして暗黙的にその上のHadoopスタックによってテストされます。

HDFSのアクションは、Unixファイルシステムアクションのアクションとリターンコードを参考に、POSIXファイルシステムの動作をモデルにしています。それでも、HDFSがPOSIXファイルシステムの期待される動作と異なる点があります。

バンドルされているS3A FileSystemクライアントは、AmazonのS3オブジェクトストア(「ブロッブストア」)をFileSystem APIを介してアクセスできるようにします。Azure ABFS、WASB、ADLオブジェクトストレージFileSystemは、MicrosoftのAzureストレージと通信します。これらはすべて、特に一貫性保証と操作の原子性に関して、異なる動作を持つオブジェクトストアにバインドされます。

「ローカル」FileSystemは、プラットフォームの基盤となるファイルシステムへのアクセスを提供します。その動作はオペレーティングシステムによって定義され、HDFSとは異なる場合があります。ローカルファイルシステムの癖の例としては、大文字と小文字の区別、別のファイルの上にファイルを名前変更しようとしたときの動作、ファイルの末尾を超えて`seek()`できるかどうかなどがあります。

Apache Hadoopとの互換性を主張するサードパーティによって実装されたファイルシステムもあります。正式な互換性スイートはないため、独自の互換性テストの形式以外で互換性を宣言する方法はありません。

これらのドキュメントは、互換性の規範的な定義を提供しようとするものでは*ありません*。関連するテストスイートに合格しても、アプリケーションの正しい動作は*保証されません*。

テストスイートが定義するのは、期待される一連のアクションです。これらのテストに失敗すると、潜在的な問題が明らかになります。

契約テストの各側面を設定可能にすることにより、ファイルシステムが標準契約の一部からどのように逸脱するかを宣言することができます。これは、ファイルシステムのユーザーに伝えることができる情報です。

命名規則

このドキュメントは、MUST、MUST NOT、MAY、SHALLの使用に関してRFC 2119のルールに従います。MUST NOTは規範的なものとして扱われます。

Hadoop FileSystem APIの暗黙的な前提条件

元の`FileSystem`クラスとその使用法は、暗黙的な前提条件のセットに基づいています。主に、HDFSが基盤となるFileSystemであり、POSIXファイルシステムの動作のサブセット(または少なくともLinuxファイルシステムによって提供されるPOSIXファイルシステムAPIとモデルの実装)を提供することです。

APIに関係なく、すべてのHadoop互換ファイルシステムは、Unixで実装されたファイルシステムのモデルを提示することが期待されています。

  • ファイルとディレクトリを持つ階層的なディレクトリ構造です。

  • ファイルには、ゼロバイト以上のデータが含まれています。

  • ファイルの下にファイルやディレクトリを置くことはできません。

  • ディレクトリには、ゼロ個以上のファイルが含まれています。

  • ディレクトリエントリ自体にはデータがありません。

  • ファイルに任意のバイナリデータを書き込むことができます。ファイルの内容がクラスタの内外から読み取られると、データが返されます。

  • 1つのファイルに数十ギガバイトのデータを保存できます。

  • ルートディレクトリ `/` は常に存在し、名前を変更することはできません。

  • ルートディレクトリ `/` は常にディレクトリであり、ファイル書き込み操作によって上書きすることはできません。

  • ルートディレクトリを再帰的に削除しようとすると、その内容は削除されます(権限の不足を除く)が、ルートパス自体は削除されません。

  • ディレクトリをそれ自体の下に名前変更/移動することはできません。

  • ソースファイル自体以外の既存のファイルの上にディレクトリを名前変更/移動することはできません。

  • ディレクトリリストには、ディレクトリ内のすべてのデータファイルが返されます(つまり、非表示のチェックサムファイルが存在する可能性がありますが、すべてのデータファイルがリストされます)。

  • ディレクトリリスト内のファイルの属性(例:所有者、長さ)は、ファイルの実際の属性と一致し、開かれたファイル参照からのビューと一致しています。

  • セキュリティ:呼び出し元に操作の権限がない場合、操作は失敗し、エラーが発生します。

パス名

  • パスは、 `"/"` で区切られたパス要素で構成されます。

  • パス要素は、1文字以上のUnicode文字列です。

  • パス要素には、文字 `":"` または `"/"` を含めてはなりません(MUST NOT)。

  • パス要素には、ASCII / UTF-8値0〜31の文字を含めないでください(SHOULD NOT)。

  • パス要素は `"."` または `".." `であってはなりません(MUST NOT)。

  • また、Azureブロッブストアのドキュメントでは、パスに末尾の `"."` を使用しないでください(.NET URIクラスがそれを削除するため)。

  • パスは、Unicodeコードポイントに基づいて比較されます。

  • 大文字と小文字を区別しない比較やロケール固有の比較を使用してはなりません(MUST NOT)。

セキュリティに関する前提条件

セキュリティに関する特別なセクションを除いて、このドキュメントでは、クライアントがFileSystemに完全にアクセスできることを前提としています。したがって、リストの項目のほとんどは、「ユーザーが指定されたパラメータとパスで操作を実行する権利を持っていると仮定して」という条件を追加していません。

ユーザーにセキュリティ権限がない場合の障害モードは指定されていません。

ネットワークに関する前提条件

このドキュメントでは、すべてのネットワーク操作が成功すると想定しています。すべてのステートメントは、「ネットワークの可用性の問題により操作が失敗しないと仮定して」と修飾されていると見なすことができます。

  • ネットワーク障害後のFileSystemの最終状態は未定義です。

  • ネットワーク障害後のFileSystemの即時一貫性状態は未定義です。

  • ネットワーク障害をクライアントに報告できる場合、障害は`IOException`のインスタンスまたはそのサブクラスでなければなりません(MUST)。

  • 例外の詳細には、経験豊富なJava開発者*または*運用チームが診断を開始するのに適した診断情報を含める必要があります(SHOULD)。たとえば、ConnectionRefused例外の送信元と送信先のホスト名とポート。

  • 例外の詳細には、経験の浅い開発者が診断を開始するのに適した診断情報を含めることができます(MAY)。たとえば、Hadoopは、TCP接続要求が拒否されたときにConnectionRefusedへの参照を含めようとします。

Hadoop互換ファイルシステムのコアとなる期待事項

Hadoop互換FileSystemのコアとなる期待事項は次のとおりです。一部のFileSystemは、これらの期待事項のすべてを満たしていません。そのため、一部のプログラムは期待どおりに動作しない場合があります。

原子性

アトミックでなければならない操作がいくつかあります。これは、多くの場合、クラスタ内のプロセス間のロック/排他アクセスを実装するために使用されるためです。

  1. ファイルの作成。`overwrite`パラメータがfalseの場合、チェックと作成はアトミックでなければなりません(MUST)。
  2. ファイルの削除。
  3. ファイルの名前変更。
  4. ディレクトリの変更。
  5. `mkdir()`を使用した単一ディレクトリの作成。
  • 再帰的なディレクトリ削除はアトミックである**場合がある**。HDFSはアトミックな再帰的ディレクトリ削除を提供するが、他のHadoopファイルシステム(ローカルファイルシステムを含む)は、そのような保証を提供しない。

他のほとんどの操作は、アトミック性に関する要件や保証がない。

整合性

Hadoopファイルシステムの整合性モデルは、従来のローカルPOSIXファイルシステムと同様に*1コピー更新セマンティクス*である。NFSでさえ、変更の伝播速度に関する制約を緩和していることに注意すること。

  • 作成。 新しく作成されたファイルを書き込む出力ストリームに対する`close()`操作が完了すると、ファイルのメタデータと内容を照会するクラスタ内操作は、ファイルとそのデータを**直ちに**認識**しなければならない**。

  • 更新。 新しく作成されたファイルを書き込む出力ストリームに対する`close()`操作が完了すると、ファイルのメタデータと内容を照会するクラスタ内操作は、新しいデータを**直ちに**認識**しなければならない**。

  • 削除。 "/"以外のパスに対する`delete()`操作が正常に完了すると、そのパスは表示またはアクセス**できない**。具体的には、`listStatus()`、`open()`、`rename()`、および`append()`操作は**失敗しなければならない**。

  • 削除してから作成。 ファイルが削除され、同じ名前の新しいファイルが作成されると、新しいファイルは**直ちに**表示され、その内容はファイルシステムAPIを介してアクセス**できる**。

  • 名前変更。 `rename()`が完了した後、新しいパスに対する操作は**成功しなければならない**。古いパスに対してデータにアクセスしようとすると**失敗しなければならない**。

  • クラスタ内の整合性セマンティクスは、クラスタ外の整合性セマンティクスと**同じでなければならない**。アクティブに操作されていないファイルを照会するすべてのクライアントは、その場所に関わらず、同じメタデータとデータを参照**しなければならない**。

並行性

データへの分離アクセスは保証されない。1つのクライアントがリモートファイルと対話し、別のクライアントがそのファイルを変更した場合、変更が表示される場合と表示されない場合がある。

操作と障害

  • すべての操作は、最終的に成功または失敗のいずれかで完了**しなければならない**。

  • 操作の完了時間は未定義であり、実装とシステムの状態によって異なる場合がある。

  • 操作は`RuntimeException`またはそのサブクラスをスロー**する場合がある**。

  • 操作は、すべてのネットワーク、リモート、および高レベルの問題を`IOException`またはそのサブクラスとして発生させる**べき**であり、そのような問題に対して`RuntimeException`を発生させる**べきではない**。

  • 操作は、操作の特定の戻りコードではなく、発生した例外によって障害を報告**すべき**である。

  • 本文中で、`IOException`などの例外クラスが指定されている場合、発生した例外は、指定された例外のインスタンスまたはサブクラスである**場合がある**。スーパークラスであっては**ならない**。

  • 操作がクラスに実装されていない場合、実装は`UnsupportedOperationException`をスロー**しなければならない**。

  • 実装は、成功するまで失敗した操作を再試行**してもよい**。再試行する場合、一連の操作間の*happens-before*関係が、記載されている整合性とアトミック性の要件を満たすように再試行**すべき**である。この例については、HDFS-4849を参照すること。HDFSは、他の呼び出し元が観測できる再試行機能を実装していない。

未定義の容量制限

ファイルシステムの容量には、明示的に定義されていない制限がいくつかある。

  1. ディレクトリ内の最大ファイル数。

  2. ディレクトリ内の最大ディレクトリ数。

  3. ファイルシステム内のエントリ(ファイルとディレクトリ)の最大合計数。

  4. ディレクトリ内のファイル名の最大長(HDFS:8000)。

  5. `MAX_PATH` - ファイルを参照するディレクトリツリー全体の全長。ブロブストアは約1024文字で停止する傾向がある。

  6. パスの最大深度(HDFS:1000ディレクトリ)。

  7. 単一ファイルの最大サイズ。

未定義のタイムアウト

操作のタイムアウトは、以下を含めてまったく定義されていない。

  • ブロッキングFS操作の最大完了時間。MAPREDUCE-972は、低速なS3の名前変更で`distcp`がどのように破損したかを説明している。

  • アイドル状態の読み取りストリームが閉じられるまでのタイムアウト。

  • アイドル状態の書き込みストリームが閉じられるまでのタイムアウト。

ブロッキング操作のタイムアウトは、実際にはHDFSで可変である。サイトとクライアントは、ファイルシステムの障害とフェールオーバーを操作の一時停止に変換するように、再試行パラメータを調整できるためである。代わりに、FS操作は「ローカルFS操作ほど高速ではないが高速である」という一般的な前提があり、データの読み取りと書き込みのレイテンシはデータ量に比例するという前提がある。クライアントアプリケーションによるこの前提は、より基本的な前提、つまりファイルシステムがネットワークレイテンシと帯域幅に関する限り「近い」ことを明らかにしている。

また、一部の操作のオーバーヘッドに関する暗黙の前提もある。

  1. `seek()`操作は高速であり、ネットワーク遅延はほとんどまたはまったくない。[これはブロブストアには当てはまらない]

  2. エントリが少ないディレクトリの場合、ディレクトリリスト操作は高速である。

  3. エントリが少ないディレクトリの場合、ディレクトリリスト操作は高速であるが、`O(エントリ数)`のコストが発生する可能性がある。Hadoop 2では、整合性を犠牲にすることなくバッファリングせずに数百万のエントリを持つディレクトリをリストするという課題に対処するために、反復リストが追加された。

  4. `OutputStream`の`close()`は、ファイル操作が成功したかどうかに関係なく高速である。

  5. ディレクトリを削除する時間は、子エントリの数のサイズに依存しない。

オブジェクトストアとファイルシステム

この仕様では、*オブジェクトストア*について言及しており、多くの場合、*ブロブストア*という用語を使用している。Hadoopは、これらの要件の多くに違反しているにもかかわらず、これらのいくつかのためのファイルシステムクライアントクラスを提供している。

特定のストアのドキュメントを参照して、特定のアプリケーションやサービスとの互換性を判断すること。

オブジェクトストアとは何か?

オブジェクトストアは、通常HTTP/HTTPS経由でアクセスされるデータストレージサービスである。`PUT`リクエストはオブジェクト/「ブロブ」をアップロードし、`GET`リクエストはオブジェクト/「ブロブ」を取得する。範囲`GET`操作では、ブロブの一部を取得できる。オブジェクトを削除するには、HTTP `DELETE`操作を呼び出す。

オブジェクトは名前(文字列、場合によっては "/" 記号を含む)で格納される。ディレクトリの概念はない。サービスプロバイダーによって課される命名スキームの制限内で、オブジェクトに任意の名前を割り当てることができる。

オブジェクトストアは常に、特定のプレフィックスを持つオブジェクトを取得する操作(適切なクエリパラメータを指定したサービスのルートに対する`GET`操作)を提供する。

オブジェクトストアは通常、可用性を優先する。HDFSネームノードに相当する単一障害点はない。また、シンプルな非POSIX APIを目指している。HTTP動詞が許可される操作である。

オブジェクトストア用のHadoopファイルシステムクライアントは、ストアがHDFSと同じ機能と操作を持つファイルシステムであるかのように見せかけようとする。これは最終的には見せかけである。特性が異なり、時折錯覚が失敗することがある。

  1. **整合性**。オブジェクトは*結果整合性*である場合がある。オブジェクトへの変更(作成、削除、更新)がすべての呼び出し元に見えるようになるまで時間がかかる可能性がある。実際、変更を行ったばかりのクライアントに変更がすぐに表示されるという保証はない。たとえば、オブジェクト`test/data1.csv`が新しいデータセットで上書きされた場合でも、更新直後に`GET test/data1.csv`呼び出しが行われると、元のデータが返される。Hadoopは、ファイルシステムが整合性があることを前提としている。つまり、作成、更新、削除はすぐに表示され、ディレクトリのリスト結果は、そのディレクトリ内のファイルに関して最新である。

  2. **アトミック性**。Hadoopは、ディレクトリ`rename()`操作と`delete()`操作がアトミックであると想定している。オブジェクトストアファイルシステムクライアントは、これらをディレクトリプレフィックスと一致する名前を持つ個々のオブジェクトに対する操作として実装する。その結果、変更は一度に1つのファイルに対して行われ、アトミックではない。処理の途中で操作が失敗した場合、オブジェクトストアの状態は部分的に完了した操作を反映する。また、クライアントコードは、これらの操作が`O(1)`であると想定していることにも注意すること。オブジェクトストアでは、`O(子エントリ数)`である可能性が高い。

  3. **耐久性**。Hadoopは、`OutputStream`実装が`flush()`操作でデータを(永続的な)ストレージに書き込むと想定している。オブジェクトストア実装は、書き込まれたすべてのデータをローカルファイルに保存する。このファイルは、最終的な`close()`操作でオブジェクトストアに`PUT`されるだけである。その結果、不完全な操作や失敗した操作からの部分的なデータは存在しない。さらに、書き込みプロセスは`close()`操作で開始されるだけなので、その操作はアップロードするデータ量に比例し、ネットワーク帯域幅に反比例する時間がかかる場合がある。また、失敗する可能性もある。無視するよりもエスカレーションする方が良い失敗である。

  4. **認証**。Hadoopは、`FileStatus`クラスを使用して、所有者、グループ、およびパーミッションを含む、ファイルとディレクトリの主要なメタデータを表現する。オブジェクトストアには、このメタデータを永続化するための実行可能な方法がない場合があるため、スタブ値で`FileStatus`を設定する必要がある場合がある。オブジェクトストアがこのメタデータを永続化する場合でも、従来のファイルシステムと同じ方法でファイル認証を実施することは、オブジェクトストアにとって実現可能ではない場合がある。オブジェクトストアがこのメタデータを永続化できない場合、推奨される規則は次のとおりである。

    • ファイルの所有者は現在のユーザーとして報告される。
    • ファイルグループも現在のユーザーとして報告される。
    • ディレクトリのパーミッションは777として報告される。
    • ファイルのパーミッションは666として報告される。
    • 所有権とパーミッションを設定するファイルシステムAPIは、エラーなしで正常に実行されるが、ノーオペレーションである.

これらの特性を持つオブジェクトストアは、HDFSの直接の代替として使用することはできない。この仕様に関して、指定された操作の実装は、必要な実装と一致しない。Hadoop開発コミュニティによってサポートされていると見なされるが、HDFSと同じ程度ではない。

タイムスタンプ

`FileStatus`エントリには、変更時刻とアクセス時刻がある。

  1. これらのタイムスタンプがいつ設定されるか、および有効かどうかについての正確な動作は、ファイルシステムによって、そして場合によってはファイルシステムの個々のインストールによって異なる。
  2. タイムスタンプの粒度も、ファイルシステムと、場合によっては個々のインストールに固有である。

HDFSファイルシステムは、書き込み中に変更時刻を更新しない。

具体的には

  • `FileSystem.create()`作成:ゼロバイトファイルがリストされる。変更時刻は、ネームノードで確認された現在の時刻に設定される。
  • create() 呼び出しで返される出力ストリームを介してファイルに書き込みます。更新時刻は*変更されません*。
  • OutputStream.close() が呼び出されると、残りのすべてのデータが書き込まれ、ファイルが閉じられ、NameNode がファイルの最終サイズで更新されます。更新時刻は、ファイルが閉じられた時刻に設定されます。
  • append() 操作を介して追加のためにファイルを開いても、出力ストリームで close() 呼び出しが行われるまで、ファイルの更新時刻は変更されません。
  • FileSystem.setTimes() を使用して、ファイルの時刻を明示的に設定できます。
  • ファイルの名前を変更しても、更新時刻は変更されませんが、ソースディレクトリとデスティネーションディレクトリの更新時刻は更新されます。
  • めったに使用されない操作:FileSystem.concat()createSnapshot()createSymlink()、および truncate() はすべて更新時刻を更新します。
  • アクセス時間の粒度はミリ秒単位で dfs.namenode.access.time.precision に設定されます。デフォルトの粒度は 1 時間です。精度がゼロに設定されている場合、アクセス時間は記録されません。
  • 更新時刻またはアクセス時刻が設定されていない場合、その FileStatus フィールドの値は 0 になります。

他のファイルシステムは異なる動作をする場合があります。特に、

  • アクセス時間がサポートされている場合とサポートされていない場合があります。基盤となる FS がアクセス時間をサポートしている場合でも、パフォーマンス上の理由から、このオプションは無効になっていることがよくあります。
  • タイムスタンプの粒度は実装固有の詳細です。

オブジェクトストアは、時間についてさらに曖昧な見方をしており、「状況によって異なります」と要約できます。

  • タイムスタンプの粒度は、HTTP HEAD および GET リクエストで返されるタイムスタンプの粒度である 1 秒になる可能性があります。
  • アクセス時間は設定されていない可能性があります。つまり、FileStatus.getAccessTime() == 0 です。
  • 新しく作成されたファイルの更新タイムスタンプは、create() 呼び出しの時刻、または PUT リクエストが開始された実際の時刻である場合があります。これは、FileSystem.create() 呼び出し、最後の OutputStream.close() 操作、またはその間のいずれかの時点である可能性があります。
  • 更新時刻は、close() 呼び出しでは更新されない場合があります。
  • タイムスタンプは、UTC またはオブジェクトストアのタイムゾーンである可能性があります。クライアントが異なるタイムゾーンにある場合、オブジェクトのタイムスタンプはクライアントの時刻よりも前または後になる場合があります。
  • ファイルの更新時刻は、多くの場合、作成時刻と同じです。
  • ファイルのタイムスタンプを設定するための FileSystem.setTimes() 操作は無視される*可能性*があります。
  • FileSystem.chmod() は更新時刻を更新する場合があります(例:Azure wasb://)。
  • FileSystem.append() がサポートされている場合、変更と更新時刻は、出力ストリームが閉じられた後にのみ表示される可能性があります。
  • オブジェクトストア内のデータに対するアウトオブバンド操作(つまり、Hadoop FileSystem API をバイパスするオブジェクトストアへの直接リクエスト)は、異なるタイムスタンプが格納および/または返される可能性があります。
  • ディレクトリ構造の概念はしばしばシミュレートされるため、ディレクトリのタイムスタンプは*おそらく*現在のシステム時刻を使用して人工的に生成される可能性があります。
  • rename() 操作は、多くの場合 COPY + DELETE として実装されるため、名前変更されたオブジェクトのタイムスタンプは、ソースオブジェクトのタイムスタンプではなく、オブジェクトの名前変更が開始された時刻になる場合があります。
  • 正確なタイムスタンプの動作は、同じタイムストアクライアントを使用している場合でも、オブジェクトストアのインストールごとに異なる場合があります。

最後に、Apache Hadoop プロジェクトは、リモートオブジェクトストアのタイムスタンプの動作が時間の経過とともに一貫性を保つかどうかについて保証できないことに注意してください。これらはサードパーティのサービスであり、通常はサードパーティのライブラリを介してアクセスされます。

ここでの最善の戦略は、「実際に使用する予定のエンドポイントで実験すること」です。さらに、キャッシング/整合性レイヤーを使用する場合は、その機能を有効にしてテストしてください。Hadoop リリースの更新後、およびエンドポイントオブジェクトストアの更新後に再テストしてください。