YARN+Capacity Schedulerのメモリと並列処理

YARN+Capacity Schedulerにおけるメモリ計算方法とMapReduceの並列処理数について、ここまで調べたことを記しておきます。

MRv1とは全く違う考え方なので、適当にせずきっちり理解しておいた方がよいと思います。



YARNデーモンのメモリ設定

ヒープサイズ

Resource ManagerやNode Managerのことですが、メモリの設定は yarn-env.sh でやることになります。プロセスで見ると -Xmx1000m とかになっている部分ですが、当然この値を変えられるように知っておく必要があります。

一見、JAVA_HEAP_MAX を変えればよさそうに見えるのですが、これを変更しても反映されません。なので、/etc/init.d/hadoop-yarn-resourcemanager を追ってみると、/usr/lib/hadoop-yarn/bin/yarn を実行していることがわかり、その中で結局 JAVA_HEAP_MAX=-Xmx1000m で上書きしているからだとわかります。

で、少し下を見ると YARN_HEAPSIZE (intでMB) という存在がわかり、これにより値が変化することを確認できます。ただ、この変数はYARNデーモン共通のものなので、yarn-env.sh を全サーバで共通にしてしまうと、全てのデーモンのヒープサイズが同じになってしまいます(別にそれでも大丈夫ですが)。

本来、YARN_RESOURCEMANAGER_OPTS や YARN_NODEMANAGER_OPTS でそれぞれ指定すると思っていたので、共通だとなんかイヤな感じですが、今は今後変わる時のことを考えてこんな感じに yarn-env.sh に書いてあります。

実消費メモリ

ヒープサイズとは別に、Resource ManagerやNode Managerがそれぞれ、どれくらいのApplication数やContainer数を抱えた時に、どれくらいのメモリを消費するかはまだ計測していません。知っておかないとある日突然Managerさんが荒ぶりだすはずです。

最大Application数と最大Container数は、NodeMangerの総メモリ量とContainerのメモリ割り当て量からだいたい計算できるので、事前に必要なヒープサイズはわかります。なので、あとでおよその見当はつけておきたいと思います。

それでもApplicationの内容により必要量が異なるかもですし、そもそもSLAVEサーバを増設したら実行できるApplication数も増えるので、絶対というものはないですが。


NodeManagerにおけるApplicationのプロセスフロー

  1. クライアントからのリクエストによりApplicationが開始します
  2. どこかのNodeManagerで1つApplication Masterが起動します
  3. Container(MapとReduce)を必要な分、全NodeManagerでキャパシティ分まで起動しては終了を繰り返します
  4. Containerが全て終了したら結果が出ます
  5. Application Masterが終了します
リソースが足りていれば滞りなく流れますが、リソースが足りなくなった時は、Application Managerすら立ち上げられずに待機する場合や、Containerが立ちあげられずに待機する場合があり、その場合は他のContainerが終わってリソースが空けば開始します。

それ以前に、他Containerが動いていないのに、そもそもApplication Manager分のメモリがなかったり、AMが立ち上がった後に1Container分のメモリすらなかった場合は何も進まなくなります。この状態になると完全棒立ちでジョブをぶった切るしかなくなるっぽいのですが、タイムアウトについて調べてなかったのでタイムアウト関連はTodoで。

Application Masterのメモリ

設定プロパティ

Application起動時に管理画面をF5ってると、最初の1containerとして 1.5GB のリソースが確保されます。これがApplication Masterのメモリ容量です。この1.5GBはどこからきてるのかというと、yarn.app.mapreduce.am.resource.mb という設定であり、デフォルトが 1536 になってます。

この設定を探す時、これはYARNだからと決めつけて yarn-default.xml や hive の SET -v; を見渡したけど全然無く、ようやく mapreduce.jobhistory.done-dir のHDFS上に残るXMLから掘り出したと思いきや、結局 mapred-default.xml に書いてあるという罠。

[#MAPREDUCE-3947] yarn.app.mapreduce.am.resource.mb not documented – ASF JIRA のような怒りIssueもありますが、一応 mapred-default.xml にあるということで・・・

設定値目安

Application Masterのメモリが少なすぎると、Application自体開始できずに終了します。テスト環境だと、768以上だと開始できましたが、それより低いとエラーになりました。なので最低推奨値としては 1024 になると思いますが、正直、1つのジョブを動かすごとに初期費用そんなに取られたくねぇって感じです。

とはいえ、デフォが 1536 だったり、v0.23.3 からはデフォが 2048 だったりと、おいおい低スペックで大量サーバがコンセプトじゃなかったのかい って思いますが、まぁこれは動かす処理によって様々でしょうから、頑張って調整してみてください。

Container(Map/Reduce)のメモリ

設定プロパティ

Mapが mapreduce.map.memory.mb で、Reduceが mapreduce.reduce.memory.mb になります。リソース計算ではこの値がきっかり使用されます(裏ルール適用を除く)。

mapreduce.map.java.optsmapreduce.reduce.java.opts はプロセスのオプションが書けますが、 -Xmx1024M のようにJavaの最大ヒープサイズを上のメモリ値と同じにしておけばいいんじゃないでしょうか。

あと、古くからの mapred.child.java.opts は今のところ使われる気配がありません。南無。

設定値目安

目安はMRv1からの感覚と同じでいいと思います。低すぎたら途中で落っこちるし、高すぎたら勿体無いし。動かす処理によって必要量が違うし、という感じ。

ただ、後述しますが、並列処理数と密接に関わってくるので、結果的には最低値の目安が 512 とか 1024 になって、上限の方は並列計算のために目安がなく、とても高く設定する場合がありそうです。

Containerメモリ設定の裏ルール

色々いじっている時に、ps でこれがMapなのかReduceなのかひと目で分かるようにわざとズラして 513, 514 とかで設定していたのですが、そうするとなぜか 640 というリソースが確保されてなんじゃこりゃ、と。

ピンときて調整してみると、Containerのメモリは 128 の倍数に繰り上げられるようで、128, 256, 384, 512, 640, … に強制変換されます。なので、513 も 639 も 640 になります。

これにハマると、管理画面の Containers RunningNum Containers の数字が食い違ったりします。Containers Running は裏で変換された値を元に実際に動いているContainer数で、Num Containers は設定値を元に計算したContainer数のような、そんな印象でした(ソース追っかけたわけじゃないので真実は知らんぉ)。

リソースの設定と合計値

ここでいうリソースとは、メモリ容量のことです。Node Mangerごとに yarn.nodemanager.resource.memory-mb で設定し、全Node Managerの合計値がClusterの総リソースとして扱われます。

この設定が肝で、いわゆるSLAVEサーバとして稼働させた場合にサーバ上でメモリを多く使うのは
  • Data Node
  • Resource Manager
  • yarn.nodemanager.resource.memory-mb = Application Masters + Containers of MR
  • OSその他の分
  • であり、最も大きく確保するからです。
    そして、計算ミスでオーバーすると容赦なくOOM Killer先生が襲いかかってきます。

    設定は目安というよりは、これ以外の分をOSメモリから引いていって残ったのを割り当てる形になると思います。

    MapReduceの並列処理数

    MRv1とは考え方が全く異なり、並列処理数は明確には指定できません。なので、mapreduce.tasktracker.map.tasks.maximummapreduce.tasktracker.reduce.tasks.maximum といった設定は全く使われません。

    MRv1での並列処理数

    旧verの話なので適当に書きますが、

    (最大Map数 + 最大Reduce数) * 1MRのメモリ容量 = 使用メモリ容量

    という感じに、並列処理数を先に決めていました。
    そしてさらにFair Schedulerでユーザやユーザ数による処理数の変動をしていました。

    なので、メモリ不足回避のためには並列処理数を計算する必要がありましたが、CPUリソースとしては明確に設定しやすいものでした。

    YARNでの並列処理数

    YARNでは、リソースありきで計算します。総リソースに対してContainerを動かせるだけ動かし、リソースオーバーは絶対にしないという考えです。なので適当な式としては

    ( Clusterリソース – (Application Masterリソース * Application数) )
      / 1MRのリソース = MRのContainer数

    ※MapとReduceのリソースは別個に設定できるけど省略してます

    例えば、総リソース8GB のClusterで 1.5GB のApplication Master が動いた場合、残り6.5GB のリソースとなります。そしてMapReduce当たりのリソースが 512MB の場合は 13Container まで同時に動けることになり、1024MB の場合は 6Containerまでとなります。

    これにさらに、Capacity Schedulerによる制限が加わって最終的な並列数となります。

    MRv1と違ってメモリ不足は確実に回避できますが、並列処理数はわかりづらくなりました。もし、小さいリソースのMRを動かすと多並列になりますし、大きいMRにすると少並列になります。

    並列数としては、CPU 8core 16thread の場合、12並列までにすべき場合や、16並列でフルに動かしたい場合、32並列で戸愚呂状態を望むポリシーもあるかもしれません。最大で何並列までになるようにするかは、Containerのリソース割り当てで調整することになり、場合によっては有り余るリソースからCPUを守るために 1MR を 4GB や 8GB で動かす羨ましいこともあるかもしれません。

    まだ不測のメモリオーバーは経験ないですが、事例はあるようです。
    [#MAPREDUCE-4191] capacity scheduler: job unexpectedly exceeds queue capacity limit by one task – ASF JIRA




    たまに処理の開始までの時間が長かったり、処理速度が遅い部分があったり、Active Nodes の数が実際より多く認識されて総リソースも嘘数値になる、とかまだまだ不満点はありますが、理論上はこんな感じだと思います。

    次回は、いよいよCapacity Schedulerによる上限設定について触れたいと思いますが、
    後ろの回になるほど調査に時間がかかってる割に需要が減っていく危惧。
    早く息子も手伝ってくれないかなぁ。