クラス org.apache.hadoop.fs.FileSystem

抽象クラスFileSystemは、Hadoopファイルシステムにアクセスするための元のクラスです。抽象でないサブクラスは、Hadoopがサポートするすべてのファイルシステムに存在します。

このインターフェースにPathを受け取るすべての操作は、相対パスをサポートする必要があります。その場合、setWorkingDirectory()で定義されたワーキングディレクトリを基準にして解決する必要があります。

したがって、すべてのクライアントに対して、状態コンポーネントPWDの概念も追加します。これは、クライアントの現在のワーキングディレクトリを表します。この状態への変更は、ファイルシステム自体には反映されません。クライアントのインスタンスに固有のものです。

実装上の注意: staticメソッドFileSystem get(URI uri, Configuration conf)は、ファイルシステムクライアントクラスの既存のインスタンスを返す場合があります。このクラスは、他のスレッドでも使用されている可能性があります。Apache Hadoopに同梱されているFileSystemの実装は、ワーキングディレクトリフィールドへのアクセスを同期しようとはしていません

不変条件

有効なFileSystemのすべての要件は、暗黙的な事前条件および事後条件と見なされます。有効なFileSystemに対するすべての操作は、同じく有効な新しいFileSystemをもたらす必要があります。

実現可能な機能

保護されたディレクトリ

HDFSには、オプションfs.protected.directoriesで宣言された保護されたディレクトリの概念があります。そのようなディレクトリまたはその親を削除または名前変更しようとすると、AccessControlExceptionが発生します。したがって、ルートディレクトリを削除しようとすると、保護されたディレクトリがある場合は、そのような例外が発生します。

述語とその他の状態アクセス操作

boolean exists(Path p)

def exists(FS, p) = p in paths(FS)

boolean isDirectory(Path p)

def isDirectory(FS, p)= p in directories(FS)

boolean isFile(Path p)

def isFile(FS, p) = p in files(FS)

FileStatus getFileStatus(Path p)

パスの状態を取得します

事前条件

if not exists(FS, p) : raise FileNotFoundException

事後条件

result = stat: FileStatus where:
    if isFile(FS, p) :
        stat.length = len(FS.Files[p])
        stat.isdir = False
        stat.blockSize > 0
    elif isDir(FS, p) :
        stat.length = 0
        stat.isdir = True
    elif isSymlink(FS, p) :
        stat.length = 0
        stat.isdir = False
        stat.symlink = FS.Symlinks[p]
    stat.hasAcl = hasACL(FS, p)
    stat.isEncrypted = inEncryptionZone(FS, p)
    stat.isErasureCoded = isErasureCoded(FS, p)

返されたパスのFileStatusの状態には、ACL、暗号化、およびイレイジャーコーディングに関する情報も含まれています。パスにACLがあるかどうかを確認するためにgetFileStatus(Path p).hasAcl()をクエリできます。パスが暗号化されているかどうかを確認するためにgetFileStatus(Path p).isEncrypted()をクエリできます。パスがイレイジャーコード化されているかどうかはgetFileStatus(Path p).isErasureCoded()で確認できます。

YARNの分散キャッシュを使用すると、アプリケーションはJob.addCacheFile()およびJob.addCacheArchive()を使用して、コンテナおよびアプリケーション間でキャッシュされるパスを追加できます。キャッシュは、暗号化されていると宣言されていない限り、ワールドリーダブルリソースパスをアプリケーション間で共有可能として扱い、異なる方法でダウンロードします。

特に委任トークンを使用する場合のコンテナ起動中の失敗を回避するために、ファイルとディレクトリの両方に対してPOSIXアクセス許可を実装していないファイルシステムとオブジェクトストアは、isEncrypted()述語に対して常にtrueを返す必要があります。これは、FileStatusインスタンスを作成するときにencryptedフラグをtrueに設定することで実現できます。

msync()

クライアントのメタデータ状態をFileSystemのメタデータサービスの最新状態と同期します。

可用性の高いFileSystemでは、スタンバイサービスを読み取り専用のメタデータレプリカとして使用できます。この呼び出しは、スタンバイレプリカからの読み取りの一貫性を保証し、古い読み取りを回避するために不可欠です。

現在、HDFSに対してのみ実装されており、その他はUnsupportedOperationExceptionをスローするだけです。

事前条件

事後条件

この呼び出しは、内部的に呼び出し時のメタデータサービスの状態を記録します。これにより、任意のメタデータレプリカからの後続の読み取りの一貫性が保証されます。クライアントが記録された状態より前のメタデータの状態にアクセスすることはないことを保証します。

HDFS実装ノート

HDFSは、アクティブNameNodeを呼び出し、その最新のジャーナルトランザクションIDを要求することにより、HAモードでmsync()をサポートします。詳細については、HDFSドキュメントHDFS Observer NameNodeからの整合性のある読み取りを参照してください。

Path getHomeDirectory()

関数getHomeDirectoryは、FileSystemと現在のユーザーアカウントのホームディレクトリを返します。

一部のFileSystemでは、パスは["/", "users", System.getProperty("user-name")]です。

ただし、HDFSの場合、ユーザー名は、クライアントをHDFSで認証するために使用される資格情報から派生します。これは、ローカルユーザーアカウント名とは異なる場合があります。

呼び出し元の実際のホームディレクトリを決定するのは、FileSystemの責任です。

事前条件

事後条件

result = p where valid-path(FS, p)

メソッドが呼び出されたときにパスが存在する必要はありません。また、存在する場合でも、ディレクトリを指す必要はありません。ただし、コードはnot isFile(FS, getHomeDirectory())が保持されていると仮定する傾向があるため、後続のコードが失敗する可能性があります。

実装ノート

  • FTPFileSystemは、リモートファイルシステムからこの値をクエリし、接続の問題がある場合は、RuntimeExceptionまたはそのサブクラスで失敗する可能性があります。操作の実行時間は制限されていません。

FileStatus[] listStatus(Path path, PathFilter filter)

パスpathの下のエントリを一覧表示します。

pathがファイルを参照し、フィルターがそれを受け入れる場合、そのファイルのFileStatusエントリが単一要素配列で返されます。

パスがディレクトリを参照する場合、呼び出しは、フィルターによって受け入れられるすべての子パスのリストを返します。これにはディレクトリ自体は含まれません。

PathFilter filterは、パスpathがフィルターの条件を満たす場合にのみaccept(path)がtrueを返すクラスです。

事前条件

パスpathは存在する必要があります

if not exists(FS, path) : raise FileNotFoundException

事後条件

if isFile(FS, path) and filter.accept(path) :
  result = [ getFileStatus(path) ]

elif isFile(FS, path) and not filter.accept(P) :
  result = []

elif isDir(FS, path):
  result = [
    getFileStatus(c) for c in children(FS, path) if filter.accepts(c)
  ]

暗黙的な不変条件: listStatus()経由で取得した子のFileStatusの内容は、同じパスへのgetFileStatus()の呼び出しからの内容と等しくなります

forall fs in listStatus(path) :
  fs == getFileStatus(fs.path)

結果の順序: リストされたエントリの順序は保証されません。HDFSは現在、アルファベット順にソートされたリストを返しますが、Posixのreaddir()やJavaのFile.listFiles() API呼び出しは、返される値の順序を定義していません。結果に一様なソート順を必要とするアプリケーションは、自身でソートを実行する必要があります。

Nullの戻り値: 3.0.0より前のローカルファイルシステムは、アクセスエラー時にnullを返していました。これは誤りであるとみなされます。アクセスエラーが発生した場合はIOExceptionを予期してください。

原子性と一貫性

listStatus()操作が呼び出し元に戻る時点では、応答に含まれる情報が最新であるという保証はありません。詳細には、任意のディレクトリの内容、任意のファイルの属性、および提供されたパスの存在などが、最新ではない可能性があります。

ディレクトリの状態は、評価プロセス中に変更される可能性があります。

  • パスPにあるエントリが作成された後、ファイルシステムに他の変更が行われる前は、listStatus(P)はファイルを検出してそのステータスを返す必要があります。

  • パスPにあるエントリが削除された後、ファイルシステムに他の変更が行われる前は、listStatus(P)FileNotFoundExceptionを発生させる必要があります。

  • パスPにあるエントリが作成された後、ファイルシステムに他の変更が行われる前は、listStatus(parent(P))の結果にgetFileStatus(P)の値を含める必要があります。

  • パスPにあるエントリが作成された後、ファイルシステムに他の変更が行われる前は、listStatus(parent(P))の結果にgetFileStatus(P)の値を含めてはなりません。

これは理論上の可能性ではなく、ディレクトリに数千のファイルが含まれている場合にHDFSで観察できます。

内容が次のようになっているディレクトリ"/d"を考えます。

a
part-0000001
part-0000002
...
part-9999999

ファイル数が多く、HDFSが各応答で部分的なリストを返す場合、リストlistStatus("/d")が操作rename("/d/a","/d/z")と同時に実行されると、結果は次のいずれかになります。

[a, part-0000001, ... , part-9999999]
[part-0000001, ... , part-9999999, z]
[a, part-0000001, ... , part-9999999, z]
[part-0000001, ... , part-9999999]

この状況はまれに発生する可能性が高いですが、発生する可能性があります。HDFSでは、このような矛盾したビューは、多数の子を持つディレクトリをリストする場合にのみ発生する可能性が高くなります。

他のファイルシステムは、より強力な一貫性保証を持つか、より容易に矛盾したデータを返す可能性があります。

FileStatus[] listStatus(Path path)

これは、DEFAULT_FILTER.accept(path) = Trueがすべてのパスに対して成り立つlistStatus(Path, DEFAULT_FILTER)と完全に同等です。

原子性と一貫性の制約は、listStatus(Path, DEFAULT_FILTER)の場合と同様です。

FileStatus[] listStatus(Path[] paths, PathFilter filter)

渡されたディレクトリのリストに見つかったすべてのファイルを列挙し、それぞれでlistStatus(path, filter)を呼び出します。

listStatus(path, filter)と同様に、結果は矛盾する可能性があります。つまり、操作中にファイルシステムの状態が変更されました。

パスが特定の順序でリストされるかどうかについての保証はありません。すべてがリストされ、リスト時に存在している必要があるだけです。

事前条件

すべてのパスが存在する必要があります。一意性に関する要件はありません。

forall p in paths :
  exists(fs, p) else raise FileNotFoundException

事後条件

結果は、パスリストで見つかったすべてのステータス要素を含み、それ以外のものは含まない配列です。

result = [listStatus(p, filter) for p in paths]

実装では、重複したエントリをマージしたり、重複したパスを認識してエントリを1回だけリストすることで操作を最適化したりすることができます。

デフォルトの実装ではリストを反復処理します。最適化は実行されません。

原子性と一貫性の制約は、listStatus(Path, PathFilter)の場合と同様です。

RemoteIterator<FileStatus> listStatusIterator(Path p)

パスの下にあるFileStatusエントリを列挙するイテレータを返します。これは、リスト全体を返すのではなくイテレータが返される点を除いて、listStatus(Path)に似ています。リスト中に他の呼び出し元がディレクトリを更新しない限り、結果はlistStatus(Path)とまったく同じです。ただし、リストの実行中に他の呼び出し元がディレクトリ内でファイルを追加/削除している場合は、原子性は保証されません。異なるファイルシステムでは、より効率的な実装を提供できる場合があります。たとえば、S3Aはページ単位でリストを作成し、ページが処理されている間に次のページを非同期でフェッチします。

初期リストが非同期になったため、バケット/パスが存在しない例外がnext()呼び出し中に後で表示される可能性があることに注意してください。

呼び出し元は、性質上インクリメンタルであるため、listStatusよりもlistStatusIteratorを使用することを推奨します。

FileStatus[] listStatus(Path[] paths)

渡されたディレクトリのリストに見つかったすべてのファイルを列挙し、それぞれでlistStatus(path, DEFAULT_FILTER)を呼び出します。ここで、DEFAULT_FILTERはすべてのパス名を受け入れます。

RemoteIterator[LocatedFileStatus] listLocatedStatus(Path path, PathFilter filter)

パスの下にあるLocatedFileStatusエントリを列挙するイテレータを返します。これは、戻り値がFileStatusLocatedFileStatusサブクラスのインスタンスであることと、リスト全体を返すのではなくイテレータが返される点を除いて、listStatus(Path)に似ています。

これは実際にはprotectedメソッドであり、listLocatedStatus(Path path)によって直接呼び出されます。これへの呼び出しは、FilterFileSystemなどの階層化されたファイルシステムを通じて委任される可能性があるため、listLocatedStatus(Path path)が別の方法で実装されている場合でも、その実装は必須と見なす必要があります。このメソッドをパブリックにすることを提案するJIRAが開かれており、将来的には実現する可能性があります。

イテレータがパスの子エントリの一貫したビューを提供する必要はありません。デフォルトの実装では、listStatus(Path)を使用してその子をリストし、その一貫性の制約はすでに文書化されています。他の実装では、列挙をさらに動的に実行できる場合があります。たとえば、子エントリのウィンドウ化されたサブセットをフェッチすることで、大きなデータ構造の構築や大きなメッセージの送信を回避します。このような状況では、ファイルシステムへの変更がより可視化される可能性が高くなります。

呼び出し元は、この呼び出しが戻ってから反復処理が完全に実行されるまでの間にファイルシステムが変更された場合、反復処理が失敗する可能性があると想定する必要があります。

事前条件

パスpathは存在する必要があります

exists(FS, path) : raise FileNotFoundException

事後条件

この操作は、listStatus(path, filter)の結果と等しい結果セットresultsetを生成します。

if isFile(FS, path) and filter.accept(path) :
  resultset =  [ getLocatedFileStatus(FS, path) ]

elif isFile(FS, path) and not filter.accept(path) :
  resultset = []

elif isDir(FS, path) :
  resultset = [
    getLocatedFileStatus(FS, c)
     for c in children(FS, path) where filter.accept(c)
  ]

操作getLocatedFileStatus(FS, path: Path): LocatedFileStatusは、LocatedFileStatusインスタンスlsのジェネレーターとして定義されます。

fileStatus = getFileStatus(FS, path)

bl = getFileBlockLocations(FS, path, 0, fileStatus.len)

locatedFileStatus = new LocatedFileStatus(fileStatus, bl)

イテレータでresultsetの要素が返される順序は未定義です。

原子性と一貫性の制約は、listStatus(Path, PathFilter)の場合と同様です。

RemoteIterator[LocatedFileStatus] listLocatedStatus(Path path)

DEFAULT_FILTERがすべてのパス名を受け入れる場合のlistLocatedStatus(path, DEFAULT_FILTER)と同等です。

RemoteIterator[LocatedFileStatus] listFiles(Path path, boolean recursive)

ディレクトリ内/下のすべてのファイルに対して、子ディレクトリを再帰的に走査する可能性のあるイテレータを作成します。

この操作の目的は、単一のRPC呼び出しで収集する必要があるデータ量を削減することにより、ファイルシステムが大規模な再帰的なディレクトリ スキャンをより効率的に処理できるようにすることです。

事前条件

exists(FS, path) else raise FileNotFoundException

事後条件

結果はイテレータであり、iterator.next()呼び出しのシーケンスからの出力は、セットiteratorsetとして定義できます。

if not recursive:
  iteratorset == listStatus(path)
else:
  iteratorset = [
    getLocatedFileStatus(FS, d)
      for d in descendants(FS, path)
  ]

関数getLocatedFileStatus(FS, d)は、listLocatedStatus(Path, PathFilter)で定義されているとおりです。

原子性と一貫性の制約は、listStatus(Path, PathFilter)の場合と同様です。

ContentSummary getContentSummary(Path path)

パスが与えられた場合、そのコンテンツの概要を返します。

getContentSummary()は最初に、指定されたパスがファイルであるかどうかを確認し、ファイルである場合は、ディレクトリのカウントに0を、ファイルのカウントに1を返します。

事前条件

exists(FS, path) else raise FileNotFoundException

事後条件

指定されたパスのディレクトリのカウントやファイルのカウントなどの情報を含むContentSummaryオブジェクトを返します。

原子性と一貫性の制約は、listStatus(Path, PathFilter)の場合と同様です。

BlockLocation[] getFileBlockLocations(FileStatus f, int s, int l)

事前条件

if s < 0 or l < 0 : raise {HadoopIllegalArgumentException, InvalidArgumentException}
  • HDFSは、無効なオフセットまたは長さに対してHadoopIllegalArgumentExceptionをスローします。これはIllegalArgumentExceptionを拡張したものです。

事後条件

ファイルシステムがロケーションを認識している場合、範囲[s:s+l]のデータが見つかる可能性のあるブロックロケーションのリストを返す必要があります。

if f == null :
    result = null
elif f.getLen() <= s:
    result = []
else result = [ locations(FS, b) for b in blocks(FS, p, s, s+l)]

ここで

  def locations(FS, b) = a list of all locations of a block in the filesystem

  def blocks(FS, p, s, s +  l)  = a list of the blocks containing data(FS, path)[s:s+l]

length(FS, f)は、isDir(FS, f)の場合には0として定義されているため、ディレクトリに対するgetFileBlockLocations()の結果は[]になることに注意してください。

ファイルシステムがロケーションを認識していない場合は、以下を返す必要があります。

  [
    BlockLocation(["localhost:9866"] ,
              ["localhost"],
              ["/default/localhost"]
               0, f.getLen())
   ] ;

*Hadoop 1.0.3のバグにより、クラスター トポロジと同じ数の要素を持つトポロジ パスを提供する必要があるため、ファイルシステムは"/default/localhost"パスを返す必要があります。これはもはや問題ではありませんが、慣例は一般的に維持されています。

BlockLocation[] getFileBlockLocations(Path P, int S, int L)

事前条件

if p == null : raise NullPointerException
if not exists(FS, p) : raise FileNotFoundException

事後条件

result = getFileBlockLocations(getFileStatus(FS, P), S, L)

long getDefaultBlockSize()

ファイルシステムの「デフォルト」のブロック サイズを取得します。これは、ジョブ ワーカー プロセス間で作業を最適に分割するための分割計算中によく使用されます。

事前条件

事後条件

result = integer > 0

この結果には定義された最小値はありませんが、ジョブの送信中に作業を分割するために使用されるため、ブロック サイズが小さすぎると、ワークロードの分割が不十分になったり、パーティションを計算する際にJobSubmissionClientとその同等のものがメモリ不足になることさえあります。

実際にはファイルをブロックに分割しないファイル システムは、効率的な処理につながる数値を返す必要があります。ファイル システムは、これをユーザーが構成できるようにすることができます (オブジェクト ストア コネクタは通常これを行います)。

long getDefaultBlockSize(Path p)

パスの「デフォルト」のブロック サイズ、つまり、ファイル システム内のパスにオブジェクトを書き込むときに使用されるブロック サイズを取得します。

事前条件

事後条件

result = integer  >= 0

通常、この操作の結果はgetDefaultBlockSize()と同じであり、指定されたパスの存在に関するチェックは行われません。

マウント ポイントをサポートするファイル システムでは、パスによって異なるデフォルト値を持つことができるため、宛先パスの特定のデフォルト値を返す必要があります。

パスが存在しない場合はエラーではありません。ファイルシステムのその部分のデフォルト/推奨値を返す必要があります。

long getBlockSize(Path p)

このメソッドは、getFileStatus(p)で返されるFileStatus構造体のブロックサイズを問い合わせるのと全く同じです。これは、ユーザーがgetFileStatus(p)を1回呼び出し、その結果を使用してファイルの複数の属性(長さ、タイプ、ブロックサイズなど)を調べることを推奨するために非推奨になりました。複数の属性が照会される場合、これはパフォーマンスの大幅な最適化になり、ファイルシステムへの負荷を軽減できます。

事前条件

if not exists(FS, p) :  raise FileNotFoundException

事後条件

if len(FS, P) > 0:  getFileStatus(P).getBlockSize() > 0
result == getFileStatus(P).getBlockSize()
  1. この操作の結果は、getFileStatus(P).getBlockSize()の値と同一でなければなりません。
  2. 推論により、長さが0より大きいファイルの場合、> 0でなければなりません。

状態変更操作

boolean mkdirs(Path p, FsPermission permission)

ディレクトリとそのすべての親を作成します。

事前条件

パスはディレクトリであるか、存在しない必要があります。

 if exists(FS, p) and not isDir(FS, p) :
     raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]

祖先がファイルであってはなりません。

forall d = ancestors(FS, p) : 
    if exists(FS, d) and not isDir(FS, d) :
        raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]

事後条件

FS' where FS'.Directories' = FS.Directories + [p] + ancestors(FS, p)
result = True

FileSystemのディレクトリ、ファイル、シンボリックリンクの排他条件の要件を満たす必要があります。

パスの存在とタイプ、およびディレクトリ作成のプローブはアトミックでなければなりません。mkdirs(parent(F))を含む結合された操作は、アトミックである可能性があります。

戻り値は、新しいディレクトリが作成されなくても常にtrueです(これはHDFSで定義されています)。

FSDataOutputStream create(Path, ...)

FSDataOutputStream create(Path p,
      FsPermission permission,
      boolean overwrite,
      int bufferSize,
      short replication,
      long blockSize,
      Progressable progress) throws IOException;

事前条件

上書きなしで作成する場合、ファイルは存在してはなりません。

if not overwrite and isFile(FS, p)  : raise FileAlreadyExistsException

ディレクトリへの書き込みまたは上書きは失敗する必要があります。

if isDir(FS, p) : raise {FileAlreadyExistsException, FileNotFoundException, IOException}

祖先がファイルであってはなりません。

forall d = ancestors(FS, p) : 
    if exists(FS, d) and not isDir(FS, d) :
        raise [ParentNotDirectoryException, FileAlreadyExistsException, IOException]

ファイルシステムは、FSが読み取り専用(HDFS)、ブロックサイズが許可される最小値を下回っている(HDFS)、レプリケーション数が範囲外(HDFS)、名前空間またはファイルシステムのクォータを超えている、予約された名前など、他の理由でリクエストを拒否する場合があります。すべての拒否は、IOExceptionまたはそのサブクラスである必要があり、RuntimeExceptionまたはサブクラスである場合があります。たとえば、HDFSはInvalidPathExceptionを発生させる可能性があります。

事後条件

FS' where :
   FS'.Files'[p] == []
   ancestors(p) is-subset-of FS'.Directories'

result = FSDataOutputStream

すべてのユーザーから見える、指定されたパスの最後に0バイトのファイルが存在する必要があります。

更新された(有効な)FileSystemには、mkdirs(parent(p))によって作成されたパスのすべての親ディレクトリが含まれている必要があります。

結果はFSDataOutputStreamであり、その操作を通じて、FS.Files[p]の更新された値を持つ新しいファイルシステム状態を生成できます。

返されたストリームの動作については、出力で説明します。

実装ノート

  • 一部の実装では、作成をファイルが存在するかどうかのチェックと実際の作成に分割します。これは、操作がアトミックではないことを意味します。overwrite==trueでファイルを作成するクライアントは、2つのテストの間に別のクライアントによってファイルが作成された場合、失敗する可能性があります。

  • S3Aおよび潜在的に他のオブジェクトストアコネクタは、出力ストリームのclose()操作が完了するまで、現在FSの状態を変更しません。これは、オブジェクトストアの動作とファイルシステムの動作との間の大きな違いです。これにより、複数のクライアントがoverwrite=falseでファイルを作成し、ファイル/ディレクトリのロジックを混乱させる可能性があります。特に、create()を使用してファイルへの排他ロックを取得する(エラーなしでファイルを作成した人がロックの保持者と見なされる)ことは、オブジェクトストアを操作する場合は安全なアルゴリズムではない可能性があります。

  • オブジェクトストアは、ファイルが作成されたときにマーカーとして空のファイルを作成する場合があります。ただし、overwrite=trueセマンティクスを持つオブジェクトストアは、これをアトミックに実装しない可能性があるため、overwrite=falseでファイルを作成することは、プロセス間の暗黙的な除外メカニズムとして使用できません。

  • ローカルファイルシステムは、ディレクトリ上にファイルを作成しようとするとFileNotFoundExceptionを発生させるため、この前提条件が失敗した場合に発生する可能性のある例外としてリストされています。

  • 対象外:シンボリックリンク。シンボリックリンクの解決済みパスは、create()操作の最終パス引数として使用されます。

FSDataOutputStreamBuilder createFile(Path p)

ファイルを作成するためのパラメータを指定するためのFSDataOutputStreamBuilderを作成します。

返されたストリームの動作については、出力で説明します。

実装ノート

createFile(p)FSDataOutputStreamBuilderのみを返し、ファイルシステムをすぐに変更しません。FSDataOutputStreamBuilderbuild()が呼び出されると、ビルダーパラメータが検証され、基になるファイルシステムでcreate(Path p)が呼び出されます。build()には、create(Path p)と同じ前提条件と事後条件があります。

  • create(Path p)と同様に、builder.overwrite(false)を指定しない限り、ファイルはデフォルトで上書きされます。
  • create(Path p)とは異なり、builder.recursive()を指定しない限り、親ディレクトリはデフォルトで作成されません。

FSDataOutputStream append(Path p, int bufferSize, Progressable progress)

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

if not exists(FS, p) : raise FileNotFoundException

if not isFile(FS, p) : raise [FileAlreadyExistsException, FileNotFoundException, IOException]

事後条件

FS' = FS
result = FSDataOutputStream

戻り値:既存のリストにデータを追加することにより、エントリFS.Files[p]を更新できるFSDataOutputStream

返されたストリームの動作については、出力で説明します。

FSDataOutputStreamBuilder appendFile(Path p)

既存のファイルに追加するためのパラメータを指定するためのFSDataOutputStreamBuilderを作成します。

返されたストリームの動作については、出力で説明します。

実装ノート

appendFile(p)FSDataOutputStreamBuilderのみを返し、ファイルシステムをすぐに変更しません。FSDataOutputStreamBuilderbuild()が呼び出されると、ビルダーパラメータが検証され、基になるファイルシステムでappend()が呼び出されます。build()には、append()と同じ前提条件と事後条件があります。

FSDataInputStream open(Path f, int bufferSize)

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

if not isFile(FS, p)) : raise [FileNotFoundException, IOException]

これは重要な前提条件です。一部のファイルシステム(例:オブジェクトストア)の実装では、返されたFSDataInputStreamでの最初のread()までHTTP GET操作を延期することにより、1回のラウンドトリップを短縮できます。ただし、多くのクライアントコードは、open()操作時に実行される存在チェックに依存しています。実装は、作成時にファイルの存在をチェックする必要があります。これは、ファイルとそのデータが、次のread()または後続の任意の時点でもまだ存在することを意味するものではありません。

事後条件

result = FSDataInputStream(0, FS.Files[p])

結果は、FS.Files[p]で定義されたバイト配列へのアクセスを提供します。そのアクセスがopen()操作が呼び出された時点でのコンテンツへのアクセスであるか、FSの後続の状態でのデータへの変更をどのように取得するかは、実装の詳細です。

結果は、操作のローカルおよびリモートの呼び出し元に対して同じでなければなりません。

HDFS実装ノート

  1. HDFSは、シンボリックリンクをトラバースしようとすると、UnresolvedPathExceptionをスローする可能性があります。

  2. HDFSは、パスがメタデータに存在しても、ブロックのコピーが見つからない場合、IOException("Cannot open filename " + src)をスローします。FileNotFoundExceptionの方がより正確で役立つと思われます。

FSDataInputStreamBuilder openFile(Path path)

openFile()を参照してください。

FSDataInputStreamBuilder openFile(PathHandle)

openFile()を参照してください。

PathHandle getPathHandle(FileStatus stat, HandleOpt... options)

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

let stat = getFileStatus(Path p)
let FS' where:
  (FS.Directories', FS.Files', FS.Symlinks')
  p' in paths(FS') where:
    exists(FS, stat.path) implies exists(FS', p')

解決された時点でのFileStatusインスタンスの参照は、getPathHandle(FileStatus)の結果と同じ参照です。PathHandleは、後続の操作で、呼び出し間で不変条件が保持されるようにするために使用できます。

optionsパラメータは、たとえば、参照データまたは場所が変更された場合に、後続の呼び出し(例:open(PathHandle))が成功するかどうかを指定します。デフォルトでは、変更が発生するとエラーになります。呼び出し元は、参照が別のパスに存在する場合やデータが変更された場合でも、操作が成功するように許可する緩和を指定できます。

実装は、呼び出し元によって指定されたセマンティクスをサポートできない場合、UnsupportedOperationExceptionをスローする必要があります。オプションのデフォルトセットは次のとおりです。

Unmoved Moved
Unchanged EXACT CONTENT
Changed PATH REFERENCE

所有権、拡張属性、およびその他のメタデータへの変更は、PathHandleと一致する必要はありません。実装では、カスタム制約を使用してHandleOptパラメータのセットを拡張できます。

クライアントは、PathHandleREFERENCEを使用して名前変更を追跡する必要があることを指定します。参照の解決に失敗することがエンティティがもはや存在しないことを意味しない限り、実装はPathHandleを作成するときにUnsupportedOperationExceptionをスローする必要があります。

クライアントは、PathHandlePATHを使用してエンティティが変更されていない場合にのみ解決する必要があることを指定します。実装は、同じパスに後で配置された同一のエンティティを区別できない限り、PathHandleを作成するときにUnsupportedOperationExceptionをスローする必要があります。

事後条件

result = PathHandle(p')

実装上の注意

PathHandleの参照は、PathHandleが作成されたときではなく、FileStatusインスタンスが作成されたときの名前空間です。実装は、有効であっても、サービス提供にコストがかかるPathHandleインスタンスの作成または解決の試行を拒否する場合があります。

オブジェクトの系統が解決されない限り、オブジェクトをコピーすることで名前を変更するオブジェクトストアは、CONTENTREFERENCEをサポートすると主張してはなりません。

PathHandleインスタンスをシリアル化し、そのセマンティクスを変更せずに、1つ以上のプロセス、別のマシン、および将来の任意の位置でインスタンス化できる必要があります。実装は、その不変条件を保証できなくなった場合は、インスタンスの解決を拒否する必要があります。

HDFS実装ノート

HDFSは、ディレクトリまたはシンボリックリンクへのPathHandle参照をサポートしていません。CONTENTREFERENCEのサポートは、INodeでファイルを検索します。INodeはNameNode間で一意ではないため、フェデレーションクラスタは、他の名前空間からの参照を検出するために、PathHandleに十分なメタデータを含める必要があります。

FSDataInputStream open(PathHandle handle, int bufferSize)

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

let fd = getPathHandle(FileStatus stat)
if stat.isdir : raise IOException
let FS' where:
  (FS.Directories', FS.Files', FS.Symlinks')
  p' in FS.Files' where:
    FS.Files'[p'] = fd
if not exists(FS', p') : raise InvalidPathHandleException

実装は、getPathHandle(FileStatus)で作成時に指定された制約に従って、PathHandleの参照を解決する必要があります。

この契約を履行するためにFileSystemに必要なメタデータは、PathHandleでエンコードされる場合があります。

事後条件

result = FSDataInputStream(0, FS.Files'[p'])

返されるストリームは、open(Path)によって返されるストリームの制約を受けます。オープン時にチェックされた制約は、ストリームで保持される可能性がありますが、これは保証されていません。

たとえば、CONTENT制約で作成されたPathHandleは、open(PathHandle)が解決されたときに変更されていなかった場合、オープン後にファイルへの更新を無視するストリームを返す場合があります。

実装上の注意

実装は、サーバー側またはクライアントにストリームを返す前に、不変条件をチェックする場合があります。たとえば、実装では、ファイルを開き、getFileStatus(Path)を使用してPathHandleの不変条件を検証して、CONTENTを実装する場合があります。これにより、偽陽性が生成され、追加のRPCトラフィックが必要になります。

boolean delete(Path p, boolean recursive)

ファイル、シンボリックリンク、ディレクトリのいずれであっても、パスを削除します。recursiveフラグは、再帰的な削除を行うかどうかを示します。設定されていない場合、空でないディレクトリを削除することはできません。

ルートディレクトリの特別な場合を除き、このAPI呼び出しが正常に完了した場合、パスの末尾には何も存在しません。つまり、結果は意図したとおりです。戻り値のフラグは、ファイルシステムの状態に変更があったかどうかを呼び出し元に伝えるだけです。

:このメソッドの多くの使用例では、戻り値がfalseであるかどうかのチェックが行われ、falseの場合は例外を発生させています。たとえば、

if (!fs.delete(path, true)) throw new IOException("Could not delete " + path);

このパターンは必要ありません。コードは、delete(path, recursive) を呼び出し、ルートディレクトリの特別な場合を除いて、宛先が存在しなくなったと仮定すべきです(ルートディレクトリの特別な扱いについては後述)。

事前条件

子を持つディレクトリは、recursive == False の場合、削除できません。

if isDir(FS, p) and not recursive and (children(FS, p) != {}) : raise IOException

(HDFSはここで PathIsNotEmptyDirectoryException を発生させます。)

事後条件

存在しないパス

ファイルが存在しない場合、ファイルシステムの状態は変更されません。

if not exists(FS, p):
    FS' = FS
    result = False

結果は False にすべきです。これは、ファイルが削除されなかったことを示します。

単純なファイル

ファイルを指すパスは削除され、戻り値は True です。

if isFile(FS, p) :
    FS' = (FS.Directories, FS.Files - [p], FS.Symlinks)
    result = True
空のルートディレクトリ、recursive == False

空のルートを削除してもファイルシステムの状態は変化せず、trueまたはfalseを返す可能性があります。

if isRoot(p) and children(FS, p) == {} :
    FS ' = FS
    result = (undetermined)

ルートディレクトリを削除しようとしても、一貫した戻りコードはありません。

実装はtrueを返すようにすべきです。これにより、falseの戻り値をチェックするコードが過剰に反応するのを防ぎます。

オブジェクトストアオブジェクトストア:ルートディレクトリの削除を参照してください。

空の(ルートでない)ディレクトリ recursive == False

ルートではない空のディレクトリを削除すると、FSからパスが削除され、trueが返されます。

if isDir(FS, p) and not isRoot(p) and children(FS, p) == {} :
    FS' = (FS.Directories - [p], FS.Files, FS.Symlinks)
    result = True
空でないルートディレクトリの再帰的な削除

子を持つルートパスと recursive==True を削除すると、一般的に3つの結果が生じる可能性があります。

  1. POSIXモデルでは、ユーザーがすべてを削除する適切な権限を持っている場合、それらを自由に削除できると仮定しています(その結果、ファイルシステムは空になります)。

    if isDir(FS, p) and isRoot(p) and recursive :
        FS' = ({["/"]}, {}, {}, {})
        result = True
    
  2. HDFSは、ファイルシステムのルートの削除を許可しません。空のファイルシステムが必要な場合は、ファイルシステムをオフラインにして再フォーマットする必要があります。

    if isDir(FS, p) and isRoot(p) and recursive :
        FS' = FS
        result = False
    
  3. オブジェクトストア:オブジェクトストア:ルートディレクトリの削除を参照してください。

この仕様は特定のアクションを推奨するものではありません。ただし、POSIXモデルでは、通常のユーザーがそのルートディレクトリを削除する権限を持たないような権限モデルがあると想定されていることに注意してください。これは、システム管理者のみが実行できるアクションです。

このようなセキュリティモデルを持たないリモートファイルシステムとやり取りするファイルシステムクライアントは、データが失われる可能性が高すぎるという理由で、delete("/", true) の呼び出しを拒否する可能性があります。

オブジェクトストア:ルートディレクトリの削除

オブジェクトストアに基づくファイルシステムの実装の一部では、ルートを削除すると常にfalseが返され、ストアの状態は変更されません。

if isRoot(p) :
    FS ' = FS
    result = False

これは、recursiveフラグの状態やディレクトリの状態に関係なく行われます。

これは、ストアの内容の必然的に非アトミックなスキャンと削除を回避する簡略化です。また、操作が実際に特定のストア/コンテナ自体を削除するかどうか、およびストアのよりシンプルなアクセス許可モデルの悪影響についての混乱も回避されます。

ルートでないディレクトリの再帰的な削除

子を持つルートでないパスと recursive==true を削除すると、パスとそのすべての子孫が削除されます。

if isDir(FS, p) and not isRoot(p) and recursive :
    FS' where:
        not isDir(FS', p)
        and forall d in descendants(FS, p):
            not isDir(FS', d)
            not isFile(FS', d)
            not isSymlink(FS', d)
    result = True

原子性

  • ファイルの削除はアトミックなアクションでなければなりません。

  • 空のディレクトリの削除はアトミックなアクションでなければなりません。

  • ディレクトリツリーの再帰的な削除はアトミックでなければなりません。

実装ノート

  • オブジェクトストアや、ディレクトリツリーがエミュレートされるその他の非従来型のファイルシステムは、delete() を再帰的なリストとエントリごとの削除操作として実装する傾向があります。これにより、O(1)のアトミックなディレクトリ削除に対するクライアントアプリケーションの期待が損なわれ、ストアをHDFSの代替としてドロップインで使用することができなくなる可能性があります。

boolean rename(Path src, Path d)

仕様の観点から、rename() はファイルシステム内で最も複雑な操作の1つです。

実装の観点から、falseを返すか、例外を発生させるかについての曖昧さが最も多い操作の1つです。

リネームには、宛先パスの計算が含まれます。宛先が存在し、それがディレクトリの場合、リネームの最終的な宛先は、宛先+ソースパスのファイル名になります。

let dest = if (isDir(FS, d) and d != src) :
        d + [filename(src)]
    else :
        d

事前条件

宛先パスに対するすべてのチェックは、最終的な dest パスが計算された後に行う必要があります。

ソース src が存在する必要があります。

exists(FS, src) else raise FileNotFoundException

destsrc の子孫であってはなりません。

if isDescendant(FS, src, dest) : raise IOException

これにより、isRoot(FS, src) の特殊なケースが暗黙的にカバーされます。

dest はルートであるか、存在する親を持っている必要があります。

isRoot(FS, dest) or exists(FS, parent(dest)) else raise IOException

宛先の親パスはファイルであってはなりません。

if isFile(FS, parent(dest)) : raise IOException

これにより、親のすべての上位要素が暗黙的にカバーされます。

宛先パスの末尾に既存のファイルがあってはなりません。

if isFile(FS, dest) : raise FileAlreadyExistsException, IOException

事後条件

ディレクトリを自分自身にリネームする

ディレクトリを自分自身にリネームすることは何もしません。戻り値は指定されていません。

POSIXでは結果は False です。HDFSでは結果は True です。

if isDir(FS, src) and src == dest :
    FS' = FS
    result = (undefined)
ファイルを自分自身にリネームする

ファイルを自分自身にリネームすることは何もしません。結果は True です。

 if isFile(FS, src) and src == dest :
     FS' = FS
     result = True
存在しないパスにファイルをリネームする

宛先がディレクトリである場合にファイルをリネームすると、ファイルは宛先ディレクトリの子として移動し、ソースパスのファイル名要素が保持されます。

if isFile(FS, src) and src != dest:
    FS' where:
        not exists(FS', src)
        and exists(FS', dest)
        and data(FS', dest) == data (FS, source)
    result = True
ディレクトリをディレクトリにリネームする

src がディレクトリの場合、そのすべての子は dest の下に存在し、パス src とその子孫は存在しなくなります。dest の下のパスの名前は、src の下のパスの名前と一致し、内容も一致します。

if isDir(FS, src) and isDir(FS, dest) and src != dest :
    FS' where:
        not exists(FS', src)
        and dest in FS'.Directories
        and forall c in descendants(FS, src) :
            not exists(FS', c))
        and forall c in descendants(FS, src) where isDir(FS, c):
            isDir(FS', dest + childElements(src, c)
        and forall c in descendants(FS, src) where not isDir(FS, c):
                data(FS', dest + childElements(s, c)) == data(FS, c)
    result = True
親パスが存在しないパスへのリネーム
  not exists(FS, parent(dest))

ここには一貫した動作はありません。

HDFS

結果はファイルシステムの状態への変更はなく、戻り値はfalseです。

FS' = FS; result = False

ローカルファイルシステム

結果は通常のリネームと同じですが、宛先の親ディレクトリも存在するという追加の(暗黙的な)機能があります。

exists(FS', parent(dest))

S3Aファイルシステム

結果は通常のリネームと同じですが、宛先の親ディレクトリが存在するという追加の(暗黙的な)機能があります。exists(FS', parent(dest))

parent(dest) がファイルである場合はチェックして拒否しますが、その他の上位要素のチェックはありません。

その他のファイルシステム

その他のファイルシステムは、操作を厳密に拒否し、FileNotFoundException を発生させます。

同時実行の要件
  • rename() のコア操作(ファイルシステム内のあるエントリを別のエントリに移動する)は、アトミックでなければなりません。一部のアプリケーションは、これをデータへのアクセスを調整する方法として利用しています。

  • 一部のファイルシステムの実装では、リネームの前後に宛先ファイルシステムに対するチェックを実行します。この例の1つは、ローカルデータへのチェックサム付きアクセスを提供する ChecksumFileSystem です。シーケンス全体がアトミックではない場合があります。

実装ノート

読み取り、書き込み、または追記のために開いているファイル

開いているファイルに対する rename() の動作は指定されていません。許可されるかどうか、開いているストリームからの読み取りまたは書き込みの後の試行がどうなるか

ディレクトリを自分自身にリネームする

ディレクトリを自分自身にリネームしたときの戻りコードは指定されていません。

宛先が存在し、ファイルである

既存のファイルの上にファイルをリネームすることは、失敗として指定され、例外が発生します。

  • ローカルファイルシステム:リネームが成功します。宛先ファイルはソースファイルに置き換えられます。

  • HDFS:リネームが失敗し、例外は発生しません。代わりに、メソッド呼び出しは単にfalseを返します。

ソースファイルが見つからない

ソースファイル src が存在しない場合は、FileNotFoundException を発生させる必要があります。

HDFSは例外を発生させずに失敗します。rename() は単にfalseを返します。

FS' = FS
result = false

ここでのHDFSの動作は、複製する機能と見なすべきではありません。FileContext は、例外を発生させるように動作を明示的に変更しました。また、そのアクションを DFSFileSystem 実装に遡って適用することは、継続的な議論のテーマとなっています。

void concat(Path p, Path sources[])

複数のブロックを結合して1つのファイルを作成します。これは、現在HDFSのみが実装している、ほとんど使用されない操作です。

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

if not exists(FS, p) : raise FileNotFoundException

if sources==[] : raise IllegalArgumentException

すべてのソースは同じディレクトリにある必要があります。

for s in sources: if parent(S) != parent(p) raise IllegalArgumentException

すべてのブロックサイズはターゲットのサイズと一致する必要があります。

for s in sources: getBlockSize(FS, S) == getBlockSize(FS, p)

重複するパスはありません。

not (exists p1, p2 in (sources + [p]) where p1 == p2)

HDFS:最後のファイルを除くすべてのソースファイルは、完全なブロックである必要があります。

for s in (sources[0:length(sources)-1] + [p]):
  (length(FS, s) mod getBlockSize(FS, p)) == 0

事後条件

FS' where:
 (data(FS', T) = data(FS, T) + data(FS, sources[0]) + ... + data(FS, srcs[length(srcs)-1]))
 and for s in srcs: not exists(FS', S)

HDFSの制限は、シーケンスでそれらを結合するためにinode参照を変更することによって concat を実装する方法の実装の詳細である可能性があります。Hadoopコアコードベースの他のファイルシステムはこのメソッドを実装していないため、実装の詳細を仕様から区別する方法はありません。

boolean truncate(Path p, long newLength)

ファイル p を指定された newLength に切り詰めます。

準拠した呼び出しを持たない実装は、UnsupportedOperationExceptionをスローする必要があります。

事前条件

if not exists(FS, p) : raise FileNotFoundException

if isDir(FS, p) : raise [FileNotFoundException, IOException]

if newLength < 0 || newLength > len(FS.Files[p]) : raise HadoopIllegalArgumentException

HDFS:ソースファイルは閉じられている必要があります。書き込みまたは追記のために開いているファイルに対しては切り捨てを実行できません。

事後条件

FS' where:
    len(FS.Files[p]) = newLength

戻り値:切り詰めが完了し、ファイルがすぐに追加のために開ける場合は true、それ以外の場合は false です。

HDFS:HDFSは、最後のブロックの長さを調整するバックグラウンドプロセスが開始されたことを示すために false を返し、クライアントはファイル更新を続行する前に、その完了を待つ必要があります。

同時実行

truncate() が発生したときに入力ストリームが開いている場合、切り詰められるファイルの部分に関連する読み取り操作の結果は未定義です。

boolean copyFromLocalFile(boolean delSrc, boolean overwrite, Path src, Path dst)

src のローカルディスク上のソースファイルまたはディレクトリは、宛先 dst のファイルシステムにコピーされます。移動後にソースを削除する必要がある場合は、delSrc フラグをTRUEに設定する必要があります。宛先がすでに存在し、宛先の内容を上書きする必要がある場合は、overwrite フラグをTRUEに設定する必要があります。

事前条件

ソースと宛先は異なる必要があります。

if src = dest : raise FileExistsException

宛先とソースは、互いに子孫であってはなりません。

if isDescendant(src, dest) or isDescendant(dest, src) : raise IOException

ソースファイルまたはディレクトリがローカルに存在する必要があります。

if not exists(LocalFS, src) : raise FileNotFoundException

ディレクトリは、上書きフラグの設定に関わらず、ファイルにコピーすることはできません。

if isDir(LocalFS, src) and isFile(FS, dst) : raise PathExistsException

上記の前提条件が例外をスローする場合を除き、宛先が存在する場合は、操作を成功させるために上書きフラグをTRUEに設定する必要があります。これにより、宛先のファイル/ディレクトリも上書きされます。

if exists(FS, dst) and not overwrite : raise PathExistsException

コピーの最終名の決定

ソースのベースパスbaseと、baseancestors(child) + childに含まれる子パスchildが与えられた場合

def final_name(base, child, dest):
    is base = child:
        return dest
    else:
        return dest + childElements(base, child)

ソースがファイルの場合の結果isFile(LocalFS, src)

ファイルの場合、宛先のデータはソースのデータになります。すべての上位ディレクトリはディレクトリです。

if isFile(LocalFS, src) and (not exists(FS, dest) or (exists(FS, dest) and overwrite)):
    FS' = FS where:
        FS'.Files[dest] = LocalFS.Files[src]
        FS'.Directories = FS.Directories + ancestors(FS, dest)
    LocalFS' = LocalFS where
        not delSrc or (delSrc = true and delete(LocalFS, src, false))
else if isFile(LocalFS, src) and isDir(FS, dest):
    FS' = FS where:
        let d = final_name(src, dest)
        FS'.Files[d] = LocalFS.Files[src]
    LocalFS' = LocalFS where:
        not delSrc or (delSrc = true and delete(LocalFS, src, false))

ローカルのLocalFSとリモートのFSの両方で、ファイルの変更がアトミックであるという期待はありません。

ソースがディレクトリの場合の結果isDir(LocalFS, src)

if isDir(LocalFS, src) and (isFile(FS, dest) or isFile(FS, dest + childElements(src))):
    raise FileAlreadyExistsException
else if isDir(LocalFS, src):
    if exists(FS, dest):
        dest' = dest + childElements(src)
        if exists(FS, dest') and not overwrite:
            raise PathExistsException
    else:
        dest' = dest

    FS' = FS where:
        forall c in descendants(LocalFS, src):
            not exists(FS', final_name(c)) or overwrite
        and forall c in descendants(LocalFS, src) where isDir(LocalFS, c):
            FS'.Directories = FS'.Directories + (dest' + childElements(src, c))
        and forall c in descendants(LocalFS, src) where isFile(LocalFS, c):
            FS'.Files[final_name(c, dest')] = LocalFS.Files[c]
    LocalFS' = LocalFS where
        not delSrc or (delSrc = true and delete(LocalFS, src, true))

操作の分離/原子性に関する期待はありません。つまり、操作の実行中にソースまたは宛先のファイルが変更される可能性があります。コピー後のファイルまたはディレクトリの最終状態については、最善を尽くす以上の保証は行いません。例:ディレクトリをコピーするとき、1つのファイルがソースから宛先に移動されることがありますが、コピー操作中に宛先の新しいファイルが更新されるのを防ぐものはありません。

実装

デフォルトのHDFS実装では、srcにある各ファイルとフォルダーを再帰的に調べて、それらを最終的な宛先(dstに対する相対位置)に順番にコピーします。

オブジェクトストアベースのファイルシステムは、上記の実装から生じる制限事項を認識し、スループットを最大化するために、並列アップロードとストアにコピーされるファイルの順序変更の可能性を利用できます。

インターフェースRemoteIterator

RemoteIteratorインターフェースは、java.util.Iteratorのリモートアクセス相当として使用され、呼び出し元がリモートデータ要素の有限シーケンスを反復処理できるようにします。

主な違いは次のとおりです

  1. Iteratorのオプションのvoid remove()メソッドはサポートされていません。
  2. サポートされているメソッドの場合、IOException例外が発生する可能性があります。
public interface RemoteIterator<E> {
  boolean hasNext() throws IOException;
  E next() throws IOException;
}

インターフェースの基本的な見方は、hasNext()がtrueの場合、next()がリスト内の次のエントリを正常に返すことを意味します

while hasNext(): next()

同様に、next()の呼び出しが成功した場合、next()の呼び出し前にhasNext()が呼び出されていれば、trueであったはずであることを意味します。

boolean elementAvailable = hasNext();
try {
  next();
  assert elementAvailable;
} catch (NoSuchElementException e) {
  assert !elementAvailable
}

next()演算子は、hasNext()が呼び出されなかった場合でも、利用可能な結果のリストを反復処理する必要があります。

つまり、NoSuchElementException例外が発生した場合にのみ終了するループを通じて、結果を列挙することができます。

try {
  while (true) {
    process(iterator.next());
  }
} catch (NoSuchElementException ignored) {
  // the end of the list has been reached
}

反復の出力は、ループと同等です

while (iterator.hasNext()) {
  process(iterator.next());
}

JVMで例外を発生させるのはコストのかかる操作であるため、while(hasNext())ループオプションの方が効率的です。(このトピックに関する議論については、並行性とリモートイテレータも参照してください)。

インターフェースの実装者は、両方の形式の反復をサポートする必要があります。テストの作成者は、両方の反復メカニズムが機能することを確認する必要があります。

反復は有限のシーケンスを返す必要があります。両方の形式のループは最終的に終了する必要があります。Hadoopコードベースのインターフェースのすべての実装は、この要件を満たしています。すべてのコンシューマーは、それが保持されると想定しています。

boolean hasNext()

後続のnext()の1回の呼び出しが例外を発生させるのではなく要素を返す場合にのみtrueを返します。

事前条件

事後条件

result = True ==> next() will succeed.
result = False ==> next() will raise an exception

next()呼び出しを介在させずにhasNext()を複数回呼び出すと、同じ値が返される必要があります。

boolean has1 = iterator.hasNext();
boolean has2 = iterator.hasNext();
assert has1 == has2;

E next()

反復の次の要素を返します。

事前条件

hasNext() else raise java.util.NoSuchElementException

事後条件

result = the next element in the iteration

next()を繰り返し呼び出すと、シーケンス全体が返されるまで、シーケンス内の後続の要素が返されます。

並行性とリモートイテレータ

ファイルシステムAPIでのRemoteIteratorの主な用途は、(場合によってはリモートの)ファイルシステム上のファイルをリストすることです。これらのファイルシステムは常に同時にアクセスされます。ファイルシステムの状態は、hasNext()プローブとnext()呼び出しの呼び出しの間で変化する可能性があります。

RemoteIteratorを反復処理中に、リモートファイルシステムでディレクトリが削除された場合、hasNext()またはnext()の呼び出しでFileNotFoundExceptionがスローされる可能性があります。

したがって、RemoteIteratorを介した堅牢な反復では、プロセス中に発生したNoSuchElementException例外をキャッチして破棄します。これは、上記のwhile(true)反復の例、またはhasNext()/next()シーケンスを介して、障害中に発生する可能性のある他の例外(たとえば、FileNotFoundException)と並行してNoSuchElementExceptionをキャッチする外側のtry/catch句を使用して行うことができます。

try {
  while (iterator.hasNext()) {
    process(iterator.next());
  }
} catch (NoSuchElementException ignored) {
  // the end of the list has been reached
}

これはHadoopコードベースでは行われていないことに注意してください。これは、堅牢なループが推奨されないことを意味するのではなく、これらのループの実装中に並行性の問題が考慮されなかったことを意味します。

インターフェースStreamCapabilities

StreamCapabilitiesは、OutputStreamInputStream、またはその他のFileSystemクラスがサポートする機能をプログラムでクエリする方法を提供します。

public interface StreamCapabilities {
  boolean hasCapability(String capability);
}

boolean hasCapability(capability)

OutputStreamInputStream、またはその他のFileSystemクラスに目的の機能がある場合にのみtrueを返します。

呼び出し元は、文字列値を使用してストリームの機能をクエリできます。可能な文字列値の表を次に示します

文字列 定数 実装 説明
hflush HFLUSH Syncable クライアントのユーザーバッファ内のデータをフラッシュします。この呼び出しが返された後、新しいリーダーはデータを表示します。
hsync HSYNC Syncable クライアントのユーザーバッファ内のデータをディスクデバイスまでフラッシュします(ただし、ディスクにキャッシュされている可能性があります)。POSIX fsyncと同様。
in:readahead READAHEAD CanSetReadahead 入力ストリームにリードアヘッドを設定します。
dropbehind DROPBEHIND CanSetDropBehind キャッシュを破棄します。
in:unbuffer UNBUFFER CanUnbuffer 入力ストリームのバッファリングを減らします。

インターフェースEtagSourceを介したEtagプローブ

FileSystemの実装では、FileStatusエントリからHTTP etagのクエリをサポートできます。その場合、要件は次のとおりです。

Etagサポートは、すべてのリスト/getFileStatus()呼び出し全体で適用される必要があります。

つまり、etagサポートを追加する場合、FileStatusまたはListLocatedStatusエントリを返すすべての操作は、EtagSourceのインスタンスであるサブクラスを返す必要があります。

リモートストアがetagを提供する場合、FileStatusインスタンスには必ずetagが必要です。

etagをサポートするには、getFileStatus()とリスト呼び出しの両方で提供される必要があります。

実装者への注意:これを実現するためにオーバーライドする必要があるコアAPIは次のとおりです

FileStatus getFileStatus(Path)
FileStatus[] listStatus(Path)
RemoteIterator<FileStatus> listStatusIterator(Path)
RemoteIterator<LocatedFileStatus> listFiles([Path, boolean)

ファイルのEtagは、すべてのリスト/getFileStatus操作で一貫している必要があります。

EtagSource.getEtag()の値は、特定のオブジェクトのgetFileStatus()の呼び出しに対してetagを返すlist*クエリで同じである必要があります。

((EtagSource)getFileStatus(path)).getEtag() == ((EtagSource)listStatus(path)[0]).getEtag()

同様に、パスのlistFiles()listStatusIncremental()、およびリスト内のすべてのファイルの親パスをリストするときに、同じ値が返される必要があります。

Etagは、異なるファイルコンテンツに対して異なる必要があります。

同じパスに書き込まれた2つの異なるデータの配列は、プローブ時に異なるetag値を持つ必要があります。これは、HTTP仕様の要件です。

ファイルのEtagは、名前変更操作全体で保持される必要があります

ファイルの名前が変更された後、((EtagSource)getFileStatus(dest)).getEtag()の値は、名前変更が行われる前の((EtagSource)getFileStatus(source)).getEtag()の値と同じである必要があります。

これはストアの実装の詳細です。AWS S3には当てはまりません。

ストアがこの要件を一貫して満たす場合にのみ、ファイルシステムはhasPathCapability()fs.capability.etags.preserved.in.renameをサポートしていることを宣言する必要があります。

ディレクトリにはetagがある場合があります

ディレクトリのエントリは、リスト/プローブ操作でetagを返す場合があります。これらのエントリは、名前変更時に保持される場合があります。

同様に、ディレクトリのエントリは、このようなエントリを提供しない場合や、名前変更時に保持しない場合、時間の経過に伴う一貫性を保証しない場合があります。

注:ルートパス「/」について特筆します。これは実際の「ディレクトリ」ではないため、誰もetagがあることを期待すべきではありません。

すべてのetag対応のFileStatusサブクラスはSerializableである必要があります。Writableの場合もあります

基本のFileStatusクラスは、SerializableWritableを実装し、そのフィールドを適切にマーシャリングします。

サブクラスは、etagを保持し、Javaシリアル化(一部のApache Sparkアプリケーションで使用)をサポートする必要があります。これは、etagフィールドを非静的にしてserialVersionUIDを追加することの問題です。

Writableサポートは、Hadoop IPC呼び出しを介したステータスデータのマーシャリングに使用されました。Hadoop 3では、org/apache/hadoop/fs/protocolPB/PBHelper.javaと非推奨のメソッドを通じて実装されています。サブクラスは、非推奨のメソッドをオーバーライドしてetagマーシャリングを追加できます。ただし、これに対する期待はなく、そのようなマーシャリングが発生する可能性は低いでしょう。

適切なetagパス機能は宣言されるべきです

  1. hasPathCapability(path, "fs.capability.etags.available")は、ファイルシステムがファイルステータス/リスト操作で有効な(空でないetag)を返す場合にのみtrueを返す必要があります。
  2. hasPathCapability(path, "fs.capability.etags.consistent.across.rename")は、etagが名前変更時に保持される場合にのみtrueを返す必要があります。

etagサポートの非要件

  • FileSystem.getFileChecksum(Path)が、オブジェクトのetagに関連するチェックサム値を返すという要件/期待はありません(値が返される場合)。
  • 同じデータが同じパスまたは異なるパスに2回アップロードされた場合、2回目のアップロードのetagは、最初のアップロードのetagと一致しない可能性があります。