HDFSにおける集中キャッシュ管理

概要

HDFSにおける*集中キャッシュ管理*は、ユーザーがHDFSによってキャッシュされる*パス*を指定できる明示的なキャッシュメカニズムです。 NameNodeは、目的のブロックをディスク上に持つDataNodeと通信し、オフヒープキャッシュにブロックをキャッシュするように指示します。

HDFSにおける集中キャッシュ管理には、多くの重要な利点があります。

  1. 明示的な固定により、頻繁に使用されるデータがメモリから削除されるのを防ぎます。これは、ワーキングセットのサイズがメインメモリのサイズを超える場合に特に重要であり、これは多くのHDFSワークロードで一般的です。

  2. DataNodeキャッシュはNameNodeによって管理されるため、アプリケーションはタスク配置の決定を行う際に、キャッシュされたブロックの場所のセットを照会できます。キャッシュされたブロックレプリカとタスクを同じ場所に配置することで、読み取りパフォーマンスが向上します。

  3. ブロックがDataNodeによってキャッシュされると、クライアントは新しい、より効率的な、ゼロコピー読み取りAPIを使用できます。キャッシュされたデータのチェックサム検証はDataNodeによって一度行われるため、クライアントはこの新しいAPIを使用する際に、事実上ゼロのオーバーヘッドが発生します。

  4. 集中キャッシングは、クラスタ全体のメモリ使用率を向上させることができます。各DataNodeでOSバッファキャッシュに依存する場合、ブロックの繰り返し読み取りにより、ブロックの*n*個すべてのレプリカがバッファキャッシュにプルされます。集中キャッシュ管理を使用すると、ユーザーは*n*個のレプリカのうち*m*個のみを明示的に固定できるため、*n-m*個のメモリを節約できます。

  5. HDFSは、Linuxプラットフォームで不揮発性ストレージクラスメモリ(SCM、永続メモリとも呼ばれます)キャッシュをサポートしています。ユーザーは、DataNodeのメモリキャッシュまたはSCMキャッシュのいずれかを有効にできます。メモリキャッシュとSCMキャッシュは、DataNode間で共存できます。現在の実装では、SCMのキャッシュデータは、DataNodeの再起動時にクリーンアップされます。SCMでの永続的なHDFSキャッシュのサポートは、将来検討されます。

ユースケース

集中キャッシュ管理は、繰り返しアクセスされるファイルに役立ちます。たとえば、結合によく使用されるHiveの小さな*ファクトテーブル*は、キャッシュに適しています。一方、*1年間のレポートクエリ*の入力をキャッシュすることは、履歴データが一度だけ読み取られる可能性があるため、あまり役に立たない可能性があります。

集中キャッシュ管理は、パフォーマンスSLAを持つ混合ワークロードにも役立ちます。優先度の高いワークロードのワーキングセットをキャッシュすることで、優先度の低いワークロードとディスクI/Oの競合が発生しないようにします。

アーキテクチャ

Caching Architecture

このアーキテクチャでは、NameNodeはクラスタ内のすべてのDataNodeオフヒープキャッシュの調整を担当します。 NameNodeは、特定のDNにキャッシュされているすべてのブロックを記述する*キャッシュレポート*を各DataNodeから定期的に受信します。 NameNodeは、DataNodeのハートビートにキャッシュコマンドとアンキャッシュコマンドをピギーバックすることによって、DataNodeキャッシュを管理します。

NameNodeは、*キャッシュディレクティブ*のセットを照会して、どのパスをキャッシュする必要があるかを判断します。キャッシュディレクティブは、fsimageおよび編集ログに永続的に保存され、JavaおよびコマンドラインAPIを介して追加、削除、および変更できます。 NameNodeは、リソース管理と権限の適用にキャッシュディレクティブをグループ化するために使用される管理エンティティである*キャッシュプール*のセットも保存します。

NameNodeは、名前空間とアクティブなキャッシュディレクティブを定期的に再スキャンして、キャッシュまたはキャッシュ解除する必要があるブロックを決定し、DataNodeにキャッシュ作業を割り当てます。再スキャンは、キャッシュディレクティブの追加または削除、キャッシュプールの削除などのユーザーアクションによってもトリガーできます。

現在、構築中、破損している、またはその他の不完全なブロックはキャッシュしていません。キャッシュディレクティブがシンボリックリンクをカバーしている場合、シンボリックリンクターゲットはキャッシュされません。

キャッシュは現在、ファイルまたはディレクトリレベルで行われます。ブロックおよびサブブロックのキャッシュは、今後の作業項目です。

概念

キャッシュディレクティブ

*キャッシュディレクティブ*は、キャッシュする必要があるパスを定義します。パスはディレクトリまたはファイルのいずれかです。ディレクトリは非再帰的にキャッシュされます。つまり、ディレクトリの第1レベルリストにあるファイルのみがキャッシュされます。

ディレクティブは、キャッシュレプリケーション係数や有効期限など、追加のパラメーターも指定します。レプリケーション係数は、キャッシュするブロックレプリカの数を指定します。複数のキャッシュディレクティブが同じファイルを参照している場合、最大のキャッシュレプリケーション係数が適用されます。

有効期限は、コマンドラインで*有効期間(TTL)*として指定されます。これは、将来の相対的な有効期限です。キャッシュディレクティブの有効期限が切れると、NameNodeはキャッシュの決定を行う際に、そのディレクティブを考慮しなくなります。

キャッシュプール

*キャッシュプール*は、キャッシュディレクティブのグループを管理するために使用される管理エンティティです。キャッシュプールには、UNIXのような*権限*があり、プールにアクセスできるユーザーとグループを制限します。書き込み権限により、ユーザーはプールにキャッシュディレクティブを追加および削除できます。読み取り権限により、ユーザーはプール内のキャッシュディレクティブと追加のメタデータを一覧表示できます。実行権限は使用されません。

キャッシュプールは、リソース管理にも使用されます。プールは最大*制限*を適用できます。これは、プールのディレクティブによって集約してキャッシュできるバイト数を制限します。通常、プール制限の合計は、クラスタのHDFSキャッシング用に予約されている合計メモリ量とほぼ等しくなります。キャッシュプールは、クラスタユーザーがキャッシュされているものとキャッシュする必要があるものを判断するのに役立つ多くの統計情報も追跡します。

プールは、最大有効期間も適用できます。これにより、プールに追加されるディレクティブの最大有効期限が制限されます。

`cacheadmin`コマンドラインインターフェース

コマンドラインでは、管理者とユーザーは、`hdfs cacheadmin`サブコマンドを介してキャッシュプールとディレクティブを操作できます。

キャッシュディレクティブは、一意の繰り返しのない64ビット整数IDによって識別されます。キャッシュディレクティブが後で削除された場合でも、IDは再利用されません。

キャッシュプールは、一意の文字列名で識別されます。

キャッシュディレクティブコマンド

addDirective

使用方法:`hdfs cacheadmin -addDirective -path <path> -pool <pool-name> [-force] [-replication <replication>] [-ttl <time-to-live>]`

新しいキャッシュディレクティブを追加します。

<path> キャッシュするパス。パスはディレクトリまたはファイルです。
<pool-name> ディレクティブが追加されるプール。新しいディレクティブを追加するには、キャッシュプールに対する書き込み権限が必要です。
-force キャッシュプールのリソース制限のチェックをスキップします。
<replication> 使用するキャッシュレプリケーション係数。デフォルトは1です。
<time-to-live> ディレクティブが有効な期間。分、時間、日で指定できます(例:30m、4h、2d)。有効な単位は[smhd]です。「never」は、期限切れにならないディレクティブを示します。指定しない場合、ディレクティブは期限切れになりません。

removeDirective

使用方法: hdfs cacheadmin -removeDirective <id>

キャッシュディレクティブを削除します。

<id> 削除するキャッシュディレクティブのID。削除するには、ディレクティブのプールに対する書き込み権限が必要です。キャッシュディレクティブIDのリストを表示するには、-listDirectivesコマンドを使用します。

removeDirectives

使用方法: hdfs cacheadmin -removeDirectives <path>

指定されたパスを持つすべてのキャッシュディレクティブを削除します。

<path> 削除するキャッシュディレクティブのパス。削除するには、ディレクティブのプールに対する書き込み権限が必要です。キャッシュディレクティブのリストを表示するには、-listDirectivesコマンドを使用します。

listDirectives

使用方法: hdfs cacheadmin -listDirectives [-stats] [-path <path>] [-pool <pool>]

キャッシュディレクティブを一覧表示します。

<path> このパスを持つキャッシュディレクティブのみを一覧表示します。読み取りアクセス権のないキャッシュプールに*path*のキャッシュディレクティブがある場合、リストには表示されません。
<pool> そのプール内のパスキャッシュディレクティブのみを一覧表示します。
-stats パスベースのキャッシュディレクティブの統計情報を一覧表示します。

キャッシュプールコマンド

addPool

使用方法: hdfs cacheadmin -addPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]

新しいキャッシュプールを追加します。

<name> 新しいプールの名前。
<owner> プールの所有者のユーザー名。デフォルトは現在のユーザーです。
<group> プールのグループ。デフォルトは現在のユーザーのプライマリグループ名です。
<mode> プールのUNIXスタイルの権限。権限は8進数で指定します(例:0755)。デフォルトでは、これは0755に設定されています。
<limit> このプール内のディレクティブによってキャッシュできる最大バイト数(合計)。デフォルトでは、制限は設定されていません。
<maxTtl> プールに追加されるディレクティブの最大許容存続時間。これは、秒、分、時間、日で指定できます(例:120s、30m、4h、2d)。有効な単位は[smhd]です。デフォルトでは、最大値は設定されていません。「never」の値は、制限がないことを指定します。

modifyPool

使用方法: hdfs cacheadmin -modifyPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]

既存のキャッシュプールのメタデータを変更します。

<name> 変更するプールの名前。
<owner> プールの所有者のユーザー名。
<group> プールのグループのグループ名。
<mode> プールの8進数表記のUnixスタイルの権限。
<limit> このプールでキャッシュできる最大バイト数。
<maxTtl> プールに追加されるディレクティブの最大許容存続時間。

removePool

使用方法: hdfs cacheadmin -removePool <name>

キャッシュプールを削除します。これにより、プールに関連付けられたパスもキャッシュから削除されます。

<name> 削除するキャッシュプールの名前。

listPools

使用方法: hdfs cacheadmin -listPools [-stats] [<name>]

1つ以上のキャッシュプールに関する情報(名前、所有者、グループ、権限など)を表示します。

-stats 追加のキャッシュプール統計情報を表示します。
<name> 指定した場合、指定されたキャッシュプールのみを一覧表示します。

ヘルプ

使用方法: hdfs cacheadmin -help <command-name>

コマンドに関する詳細なヘルプを取得します。

<command-name> 詳細なヘルプを取得するコマンド。コマンドを指定しない場合、すべてのコマンドの詳細なヘルプが出力されます。

設定

ネイティブライブラリ

ブロックファイルをメモリにロックするために、DataNodeは、Windowsでは`libhadoop.so`または`hadoop.dll`にあるネイティブJNIコードに依存します。HDFS集中型キャッシュ管理を使用している場合は、必ずJNIを有効にしてください

現在、永続メモリキャッシュには2つの実装があります。デフォルトは純粋なJavaベースの実装で、もう1つはPMDKライブラリを活用してキャッシュ書き込みとキャッシュ読み取りのパフォーマンスを向上させるネイティブ実装です。

PMDKベースの実装を有効にするには、以下の手順に従ってください。

  1. PMDKライブラリをインストールします。詳細については、公式サイトhttp://pmem.io/を参照してください。

  2. PMDKサポート付きでHadoopをビルドします。ソースコードの`BUILDING.txt`の「PMDK library build options」セクションを参照してください。

HadoopによってPMDKが正しく検出されたことを確認するには、`hadoop checknative`コマンドを実行します。

設定プロパティ

必須

DRAMキャッシュまたは永続メモリキャッシュのいずれかの以下のプロパティを必ず設定してください。DRAMキャッシュと永続キャッシュは、DataNode上で共存できないことに注意してください。

  • dfs.datanode.max.locked.memory

    これは、DataNodeがキャッシュに使用するメモリの最大量を決定します。Unixライクなシステムでは、DataNodeユーザーの「ロックインメモリサイズ」ulimit(`ulimit -l`)もこのパラメータと一致するように増やす必要があります(以下のOS制限のセクションを参照)。この値を設定する際には、DataNodeとアプリケーションのJVMヒープ、オペレーティングシステムのページキャッシュなど、他のもののためのメモリ領域も必要になることに注意してください。

    この設定は、Lazy Persist Writes機能と共有されます。DataNodeは、Lazy Persist Writesと集中型キャッシュ管理によって使用されるメモリの合計が、`dfs.datanode.max.locked.memory`で設定された量を超えないようにします。

  • dfs.datanode.pmem.cache.dirs

    このプロパティは、永続メモリのキャッシュボリュームを指定します。複数のボリュームの場合は、「,」で区切る必要があります(例:「/mnt/pmem0, /mnt/pmem1」)。デフォルト値は空です。このプロパティが設定されている場合、ボリューム容量が検出されます。また、`dfs.datanode.max.locked.memory`を設定する必要はありません。

オプション

以下のプロパティは必須ではありませんが、チューニングのために指定できます。

  • dfs.namenode.path.based.cache.refresh.interval.ms

    NameNodeは、これを後続のパスキャッシュ再スキャンの間隔(ミリ秒)として使用します。これは、キャッシュするブロックと、キャッシュする必要があるブロックのレプリカを含む各DataNodeを計算します。

    デフォルトでは、このパラメータは30000(30秒)に設定されています。

  • dfs.datanode.fsdatasetcache.max.threads.per.volume

    DataNodeは、これを新しいデータのキャッシュに使用するボリュームごとの最大スレッド数として使用します。

    デフォルトでは、このパラメータは4に設定されています。

  • dfs.cachereport.intervalMsec

    DataNodeは、これをキャッシュ状態の完全なレポートをNameNodeに送信する間隔(ミリ秒)として使用します。

    デフォルトでは、このパラメータは10000(10秒)に設定されています。

  • dfs.namenode.path.based.cache.block.map.allocation.percent

    キャッシュされたブロックマップに割り当てるJavaヒープの割合。キャッシュされたブロックマップは、チェーンハッシュを使用するハッシュマップです。キャッシュされたブロックの数が多い場合、小さいマップへのアクセスが遅くなる可能性があります。大きいマップはより多くのメモリを消費します。デフォルトは0.25%です。

  • dfs.namenode.caching.enabled

    このパラメータを使用して、NameNodeでの中央集中型キャッシュを有効/無効にできます。中央集中型キャッシュが無効になっている場合、NameNodeはキャッシュレポートを処理したり、クラスタ上のブロックキャッシュの場所に関する情報を保存したりしません。 NameNodeは、キャッシュが有効になるまでこの情報に基づいて動作しませんが、ファイルシステムメタデータにパスベースのキャッシュの場所を引き続き保存することに注意してください。このパラメータのデフォルト値はtrueです(つまり、中央集中型キャッシュが有効になっています)。

  • dfs.datanode.pmem.cache.recovery

    このパラメータは、DataNodeの起動中に永続メモリ上の以前のキャッシュのステータスを回復するかどうかを決定するために使用されます。有効になっている場合、DataNodeは永続メモリ上の以前にキャッシュされたデータのステータスを回復します。したがって、データの再キャッシュは回避されます。このプロパティが有効になっていない場合、DataNodeは永続メモリ上の以前のキャッシュ(存在する場合)をクリーンアップします。このプロパティは、永続メモリが有効になっている場合、つまり`dfs.datanode.pmem.cache.dirs`が設定されている場合にのみ機能します。

OS制限

「設定された最大ロックメモリサイズ...がDataNodeの使用可能なRLIMIT_MEMLOCK ulimitよりも大きいため、DataNodeを起動できません」というエラーが発生した場合は、オペレーティングシステムが、設定した量よりもロックできるメモリの量に低い制限を課していることを意味します。これを修正するには、DataNodeが実行される`ulimit -l`値を調整する必要があります。通常、この値は`/etc/security/limits.conf`で設定されます。ただし、使用しているオペレーティングシステムとディストリビューションによって異なります。

シェルから`ulimit -l`を実行し、`dfs.datanode.max.locked.memory`で設定した値よりも高い値、または制限がないことを示す文字列「unlimited」が返された場合、この値が正しく設定されていることがわかります。 `ulimit -l`は通常、メモリロック制限をKB単位で出力しますが、dfs.datanode.max.locked.memoryはバイト単位で指定する必要があることに注意してください。

この情報は、Windowsへのデプロイには適用されません。 Windowsには、`ulimit -l`の直接の同等物はありません。