図解 MapR のメモリ管理

MapR Hadoopディストリビューションにおいて、メモリがどのように割り当てられているかは一見わかりにくいので、図を使いながら詳細を解説していきましょう。なお、説明はMapR 5.0のYARN構成がベースになっています。YARNアプリケーションのメモリ割り当ての説明以降は、どのHadoopディストリビューションでも同じなので広く参考になると思います。

MapRのサービスのメモリ割り当て

MapRでは各ノードで管理や処理を担うプロセスを「サービス」として定義しています。一般的なHadoopにも存在するYARNのResourceManagerやNodeManagerといったサービスもあれば、HDFSの代わりにファイルシステムの機能を提供するMapR-FSやCLDBのようなサービスや、NFSサーバ機能を提供するNFSサービス、Web UI機能を提供するMapR Control Systemサービスなどが存在します。

その中で、すべてのサービスのライフサイクルを管理する役割を提供するWardenというサービスがあります。いわばサービスをまとめて管理するサービスです。Wardenサービスの重要な役割の一つに、各サービスにどれだけメモリリソースを割り当てるかを管理する機能があります。

Wardenはクラスタ起動時に物理メモリ容量を取得し、設定ファイルをもとに各サービスが利用可能なメモリ容量を計算し、サービスごとに割り当てていきます。設定ファイルの一つ/opt/mapr/conf/warden.confを見ながら、例としてZooKeeperサービスに割り当てられるメモリがどのように計算されるかを見ていきます。

service.command.zk.heapsize.percent=1
service.command.zk.heapsize.max=1500
service.command.zk.heapsize.min=256

上記はZooKeeperのメモリ割り当てを記述している部分ですが、原則としてノードの物理メモリに対するpercentの割合がそのサービスに割り当てられます。ただし、上限としてmax、下限としてminの値が決められていますので、その範囲を外れる場合は上限または下限に値が調整されます。

例として、物理メモリがそれぞれ192GB、64GB、16GBの3つのパターンについて見てみましょう。

f:id:nagixx:20150811181052p:plain

物理メモリが64GBのノードの場合は64GB×1%=655MBがZooKeeperに割り当てられます。一方、192GBのノードの場合は192GB×1%=1966MBになりますが、これは上限の1500MBを超えていますので、割り当て値は1500MBになります。同様に、16GBのノードの場合は16GB×1%=164MBで下限を下回るため、割り当て値は256MBになります。

上記の計算がすべてのサービスに対して適用されます。ノードによってインストールされるサービスが異なるため、各ノードでのメモリ割り当ても異なる結果になります。

MapR 特有のコアサービスに関しての設定は/opt/mapr/conf/warden.confファイルに記述されています。この中に、OSに割り当てるメモリの設定があります。この項目は実際にはサービスではありませんが、OSの稼働に必要なメモリ容量は計算上確保しておく必要があるため存在しています。また、MapR-FSサービスの設定には割合と下限の項目はありますが、上限はありません。

以下に、パラメータとデフォルト値の一覧を示します。

/opt/mapr/conf/warden.conf

パラメータデフォルト値説明
service.command.cldb.heapsize.percent 8 CLDBに割り当てる物理メモリの割合(%)
service.command.cldb.heapsize.max 4000 CLDBが利用可能な最大メモリ容量(MB)
service.command.cldb.heapsize.min 256 CLDBが最低限確保するメモリ容量(MB)
service.command.mfs.heapsize.percent 35 MapR-FSに割り当てる物理メモリの割合(%)
service.command.mfs.heapsize.maxpercent 85 余剰メモリ容量がある場合にMapR-FSが利用可能な最大の物理メモリの割合(%)。YARN環境では使われない
service.command.mfs.heapsize.max 512 MapR-FSが最低限確保するメモリ容量(MB)
service.command.webserver.heapsize.percent 3 MapR Control Systemに割り当てる物理メモリの割合(%)
service.command.webserver.heapsize.max 750 MapR Control Systemが利用可能な最大メモリ容量(MB)
service.command.webserver.heapsize.min 512 MapR Control Systemが最低限確保するメモリ容量(MB)
service.command.nfs.heapsize.percent 3 NFSに割り当てる物理メモリの割合(%)
service.command.nfs.heapsize.max 1000 NFSが利用可能な最大メモリ容量(MB)
service.command.nfs.heapsize.min 64 NFSが最低限確保するメモリ容量(MB)
service.command.os.heapsize.percent 10 OSに割り当てる物理メモリの割合(%)
service.command.os.heapsize.max 4000 OSが利用可能な最大メモリ容量(MB)
service.command.os.heapsize.min 256 OSが最低限確保するメモリ容量(MB)
service.command.warden.heapsize.percent 1 Wardenに割り当てる物理メモリの割合(%)
service.command.warden.heapsize.max 750 Wardenが利用可能な最大メモリ容量(MB)
service.command.warden.heapsize.min 64 Wardenが最低限確保するメモリ容量(MB)
service.command.zk.heapsize.percent 1 ZooKeeperに割り当てる物理メモリの割合(%)
service.command.zk.heapsize.max 1500 ZooKeeperが利用可能な最大メモリ容量(MB)
service.command.zk.heapsize.min 256 ZooKeeperが最低限確保するメモリ容量(MB)

YARNコンポーネントのサービスやHadoopエコシステムのサービスに関しては、/opt/mapr/conf/conf.d/ディレクトリ以下のwarden.<サービス名>.confという名前の、サービスごとの個別ファイルに設定があります。MapRコアサービスとは異なり、メモリ割り当てのデフォルト値は記載されていません。ノード上のメモリ割り当てを厳密に管理したい場合には、明示的に下記のパラメータを指定する必要があります。

/opt/mapr/conf/conf.d/warden.<サービス名>.conf

パラメータ説明
service.heapsize.percent サービスに割り当てる物理メモリの割合(%)
service.heapsize.max サービスが利用可能な最大メモリ容量(MB)
service.heapsize.min サービスが最低限確保するメモリ容量(MB)

サービス名と対応するパッケージ名は下表を参照してください。

サービス名説明パッケージ名
drill-bits Drillサービス mapr-drill
gateway MapR Gatewayサービス mapr-gateway
hue Hueサービス mapr-hue
httpfs HttpFSサービス mapr-httpfs
hbasethrift HBase Thriftサービス mapr-hbasethrift
hbase-rest HBase REST Gatewayサービス mapr-hbase-rest
historyserver YARN Job History Serverサービス mapr-historyserver
hivemeta Hive Metastoreサービス mapr-hivemetastore
hs2 HiveServer2サービス mapr-hiveserver2
nodemanager YARN Node Managerサービス mapr-nodemanager
oozie Oozieサービス mapr-oozie
resourcemanager YARN Resource Managerサービス mapr-resourcemanager
spark-master Spark Masterサービス mapr-spark-master

MapRサービス用メモリの積み上げ

このように各サービスで個別に計算した割り当てメモリサイズを積み上げて、サービス用割り当て容量の合計サイズが求められます。

ここで、CLDBおよびZooKeeperサービスが稼働するノードのみに適用する例外があります。もしどちらかのサービスがそのノードに構成されていると、1500MB(ただしサービス用割り当て容量の合計が6GB未満であれば、その25%)がマージンとして確保されます。これは、CLDBまたはZooKeeperはクラスタの稼働のための重要なプロセスであり、万が一にもメモリ不足による停止もしくは応答の中断が発生することを避け、余裕を持たせるためです。

さて、物理メモリ容量から、サービス用割り当て容量とCLDB/ZooKeeper用のマージンを差し引いた残りは、YARNアプリケーションに提供されることになります。一般的なHadoopでは、YARNアプリケーション用のメモリサイズはパラメータyarn.nodemanager.resource.memory-mb (yarn-site.xml)にてあらかじめ定義しておく必要がありますが、MapRの場合は上記の通りWardenが計算した結果が、yarn.nodemanager.resource.memory-mbパラメータとしてYARN NodeManagerに自動的に引き渡されます。

f:id:nagixx:20150811185740p:plain

具体的な計算例を見ていきましょう。64GB (=65536MB)の物理メモリを搭載したマシンがある場合、各サービスに割り当てられるメモリは次の通りになります。

サービス名percentminmax計算結果
cldb 8 256 4000 4000
mfs 35 512 - 22937
webserver 3 512 750 750
nfs 3 64 1000 1000
os 10 256 4000 4000
warden 1 64 750 655
zk 1 256 1500 655
サービス合計 33997
cldb/zkマージン 1500
YARNアプリケーション用メモリ 30039

この場合、30039MBがYARNアプリケーションに割り当てられることになります。

MapRサービスで実際に使われるメモリ容量

ところで、Wardenで計算されたサービスのメモリ割り当ては、実際に使用されるメモリ容量に反映されるのでしょうか。実は、Wardenの設定がサービス起動時にプロセスに反映されるサービスと、反映されないサービスの2通りがあります。

例えばMapR-FSサービスは、Warden設定から計算された割り当て容量がMapR-FSプロセスに渡され、実際にOSのコマンドで確認すると設定されたサイズでメモリが確保されていることがわかります。一方、NodeManagerサービスは、Warden設定が計算上利用されますが、JVM起動時の最大ヒープサイズの指定にはyarn-env.shの環境変数YARN_NODEMANAGER_HEAPSIZEが使用されます。

ということで、実際に使われるメモリ容量が指定されているプロパティを表としてまとめました。ちなみにJVMで動作するサービスの場合、指定されたメモリ容量がJVMの最大ヒープサイズとして設定されますが、JVMはメモリが必要になったときに初めて物理メモリがマップされるため、常にすべての容量が確保されるわけではありません。念のため。

サービス名プロパティデフォルト値ファイル名
cldb Warden設定
mfs Warden設定
webserver Warden設定
nfs Warden設定
os 環境による
warden JVMのデフォルト値(物理メモリの1/4)
zk JVMのデフォルト値(物理メモリの1/4)
historyserver HADOOP_JOB_HISTORYSERVER_HEAPSIZE 1000 mapred-env.sh
nodemanager YARN_NODEMANAGER_HEAPSIZE 1000 yarn-env.sh
resourcemanager YARN_RESOURCEMANAGER_HEAPSIZE 1000 yarn-env.sh
drill DRILL_MAX_DIRECT_MEMORY 8G drill-env.sh
DRILL_HEAP 4G

YARNアプリケーションのメモリ割り当て

YARNアプリケーションに対するメモリ割り当てのしくみも結構込み入っているため、正確に把握している人は意外に少ないような気がします。YARNフレームワークは汎用的なリソース管理のしくみを持っていますが、YARN上で動作する代表的なアプリケーションの1つであるMapReduce (MRv2)を例として、メモリが割り当てられる流れを見ていきます。

関連するパラメータは以下の通りです。

/opt/mapr/hadoop/hadoop-2.7.0/etc/hadoop/yarn-site.xml

パラメータデフォルト値説明
yarn.nodemanager.resource.memory-mb Wardenによる計算 YARNコンテナに割り当てることのできるメモリ容量(MB)
yarn.scheduler.minimum-allocation-mb 1024 メモリ要求に対しYARNスケジューラが割り当てる最小メモリサイズ(MB)。CapacitySchedulerの場合、割り当てるメモリサイズの単位にもなる
yarn.scheduler.increment-allocation-mb 1024 (FairSchedulerのみ)メモリ要求に対しYARNスケジューラが割り当てるメモリサイズの単位(MB)
yarn.scheduler.maximum-allocation-mb 8192 メモリ要求に対しYARNスケジューラが割り当てる最大メモリサイズ(MB)

/opt/mapr/hadoop/hadoop-2.7.0/etc/hadoop/mapred-site.xml

パラメータデフォルト値説明
yarn.app.mapreduce.am.resource.mb 1536 MRv2のApplicationMasterのコンテナのメモリサイズ(MB)
mapreduce.map.memory.mb 1024 MRv2のMap Taskのコンテナのメモリサイズ(MB)
mapreduce.reduce.memory.mb 3072 MRv2のReduce Taskのコンテナのメモリサイズ(MB)

NodeManagerが起動する際には、まずパラメータyarn.nodemanager.resource.memory-mbが利用できるメモリ容量として読み込まれます。前述の通り、MapRの場合はWardenが自動計算したサイズが使われます。

MRv2アプリケーションの実行時には、アプリケーションの処理を行うYARNコンテナに必要なメモリサイズが、ResourceManagerのYARNスケジューラに対して要求されます。MRv2のApplicationMasterを実行するコンテナのメモリサイズはyarn.app.mapreduce.am.resource.mb、Map Taskを実行するコンテナのメモリサイズはmapreduce.map.memory.mb、Reduce Taskを実行するコンテナのメモリサイズはmapreduce.reduce.memory.mbで指定された値が読み込まれ、要求が行われます。

f:id:nagixx:20150812094448p:plain

割り当てるメモリサイズはFairSchedulerの場合yarn.scheduler.increment-allocation-mb、CapacitySchedulerの場合yarn.scheduler.minimum-allocation-mbの倍数である必要があり、要求されたメモリサイズがこれらのパラメータのちょうど倍数になっていない場合には、要求を受けたYARNスケジューラは、最も近い倍数に繰り上げたサイズを割り当てます。また、メモリサイズが下限のyarn.scheduler.minimum-allocation-mbおよび上限のyarn.scheduler.maximum-allocation-mbで指定される範囲に収まっているかを確認し、範囲を外れる場合には上限または下限に値を調整します。これらは「リソースの正規化」と呼ばれます。

見方を変えると、yarn.app.mapreduce.am.resource.mb、mapreduce.map.memory.mb、mapreduce.reduce.memory.mbの値がyarn.scheduler.increment-allocation-mbやyarn.scheduler.minimum-allocation-mbの倍数になっていない場合には必要以上の容量がコンテナに割り当てられることになるため、メモリ利用効率を最大にするためにはこれらの倍数にあわせることが重要になります。

YARNスケジューラは、各ノードのNodeManagerが管理する利用可能なリソースの中から、上記の通り計算されたリソースをコンテナに割り当て、処理が実行されます。

YARNコンテナで実際に使われるメモリ容量

MapReduceアプリケーション用に設定するメモリ関連の項目には、他にも次のようなパラメータが存在します。これらはJVMで利用する最大ヒープサイズを指定していますが、なぜコンテナ用のメモリサイズとJVMの最大ヒープサイズの2種類の設定項目があるのでしょうか。 

/opt/mapr/hadoop/hadoop-2.7.0/etc/hadoop/mapred-site.xml

パラメータデフォルト値説明
yarn.app.mapreduce.am.command-opts -Xmx1024m MRv2のApplicationMasterのJVM最大ヒープサイズ
mapreduce.map.java.opts -Xmx900m MRv2のMap TaskのJVM最大ヒープサイズ
mapreduce.reduce.java.opts -Xmx2560m MRv2のReduce TaskのJVM最大ヒープサイズ

その理由は、JVMのヒープサイズはJVMの世代別GCで扱われる領域のうち、Eden、Survivor、Old領域のみを対象としており、それ以外に使われるメモリ容量は別に管理する必要があるためです。

それ以外に使用される可能性があるメモリとしては、Permanent領域(クラス/メソッド/static変数)、Cヒープ(JVM自体の内部使用メモリ)、スレッドスタック(メソッドの呼び出し階層情報/変数)、JNI呼び出しのネイティブコード、Forkされた子プロセスのメモリ、などがあります。これらが知らないうちにノード内のメモリを使い尽くしてしまうことを防ぐため、各ノードに存在するcontainer-executorというプロセスがYARNコンテナで利用されるメモリを管理・監視しています。JVM自体もcontainer-executorがForkする子プロセスであるため、container-executorはプロセスツリーをもとに使用メモリ容量を監視して、各コンテナに割り当てられたメモリサイズに収まっているかを確認し、超過していればJVMプロセスおよびその子プロセスを強制的に終了させます。

f:id:nagixx:20150812095125p:plain

では、コンテナ用のメモリサイズに対してJVMの最大ヒープサイズをどれくらいに設定しておけばよいか、ということですが、アプリケーションコードにも依存しますが、だいたいコンテナ用のメモリサイズの75〜80%程度にしておけば問題ないケースが多いです。

以上、YARNアプリケーションとしてMapReduceの例で説明をしましたが、TezやSparkなどでは異なるパラメータの設定があります。ただ、YARNスケジューラの部分の考え方は変わりませんので、参考にしていただければと思います。