Prestoのsplitスケジューラに関するメモ

3
Sadayuki Furuhashi @frsyuki

いま @taroleo がMountain Viewオフィスに来ていて、Prestoのスケジューラの改善方法について話していたのだけども、やはりPrestoのようにin-memoryでDAGな実行エンジンで多数のクエリを同時にスケジュールするのは、かなり難しい。

2014-12-18 14:22:10
Sadayuki Furuhashi @frsyuki

Prestoは最低限クラスタが落ちないようにする部分はカバーしていて、それだけでも比較して良くできているのだけども、クラスタがoverloadedなときのスケジューリングはまだ原始的。でも0.78、0.80で色々と改善が入っていて、Facebook内でかなり揉まれてる感はある

2014-12-18 14:32:10
Sadayuki Furuhashi @frsyuki

欲しいと思ったのは、クエリが2つ走っていて、それぞれがtable scan taskを100 split実行するときに、Prestoクラスタ全体としては50 splitしか実行できないような場合に、どちらかのtaskを優先するかをカスタマイズする方法。

2014-12-18 14:36:03
Sadayuki Furuhashi @frsyuki

基本的には片方のtaskを先に全力で先に終わらせた方が、平均の遅延を短縮できるので優れている。現在の実装はランダムでタスクが選ばれるので、どちらのtaskも平均して遅くなる。これはfairではあるがPrestoの用途向きではない。

2014-12-18 14:37:23
Sadayuki Furuhashi @frsyuki

LinuxのCFQのような感じで、より長くスレッドを消費しているtaskの優先順位を下げると、長期間走るバッチクエリは優先順位が下がり、対話的に走って欲しい細かいクエリは優先順位が上がりすぐに終わる。ただPrestoでは、クエリを裏で待たせると貴重なメモリが消費され続ける。

2014-12-18 14:50:47
Sadayuki Furuhashi @frsyuki

なので待たせるよりもさっさと終わらせてメモリを解放させたい。それからバッチクエリは朝9時まで!というように終了時間が厳密に決められている場合があるから、長期間走るクエリの優先順位は必ずしも下げたくない。対話型クエリの投入頻度を推測できなくてバッチクエリの終了時間を推定できなくなる

2014-12-18 14:55:12
Sadayuki Furuhashi @frsyuki

一つの案は、単位時間当たりのスレッド占有時間を制限する方法。30秒間100スレッド使ったら、次の30秒はクエリを実行できない…というような。これで1分間で平均したリソース消費量は制限されるので、気付いたらものすごくコストかかってた…ということはなくなる。

2014-12-18 14:59:15
Sadayuki Furuhashi @frsyuki

スレッド秒という単位を導入する感じ。6000スレッド秒の中でクエリをやりくりする。バッチクエリに対して2000スレッド秒を予約しておき、対話型クエリを4000スレッド秒だけ使って処理する。同一の予約領域内のクエリはFIFOでもいいし、CFQ的にスケジュールしてもいい。

2014-12-18 15:01:55
Sadayuki Furuhashi @frsyuki

ただこの方法の難点は、長期的に平均するとリソース消費量は制限されている一方で、ある瞬間のリソース消費量は一切制限されないから、クラスタのサイズが決められない。クエリが投入されたらサーバを足し、終わった瞬間に落とす…!のようなオートスケールが必要になる。

2014-12-18 15:03:26
Sadayuki Furuhashi @frsyuki

AWSの課金は1時間単位なので、30秒だけ起動していても1時間分取られるから、このクラウド時代でもクエリ単位でサーバを足すのは厳しい。と言うか30秒だとJavaの初期化時間で終わる。なので最大のリソース消費量も制限したい。だけどもバッチクエリも定時で終了させたい。

2014-12-18 15:05:53
Sadayuki Furuhashi @frsyuki

でもクラスタ全体の最大のリソース消費量と、1クエリの高速性を両立するのは、かなり無理だなぁ。1人がtable scan taskを最大100 splitまでしか実行できないことにして、10人でクラスタを共有すると、クラスタ全体で1000スレッド用意すればいいのだけども、

2014-12-18 15:12:16
Sadayuki Furuhashi @frsyuki

そうすると1人しかクエリを実行していないときに、900スレッドが無駄になる。Prestoで走るクエリは大体短いクエリで、Prestoの高速性はだいたい並列IOからきているから、10秒だけ1000スレッド全部占有できるが残り50秒は何もできない…という方が応答時間も短いし優れている

2014-12-18 15:14:34
Sadayuki Furuhashi @frsyuki

となればすべてを達成するのは無理なので、どこか良い落としどころを見つける必要があるのだけども、まずはバッチクエリと対話型クエリでスケジューリング方法を分けるところかなぁ。そうするとカスタマイズ可能なスケジューラが必須で、Prestoの改造はだいぶ必要。

2014-12-18 15:16:42
Sadayuki Furuhashi @frsyuki

TDではクエリにVERY LOW から VERY HIGHまで5段階の優先順位があるのだけども、そこにREALTIMEを足す案もあるかな。REALTIMEクエリは、最優先で開始されるが、占有できるsplitが割り当てスレッド秒までに制限される。

2014-12-18 15:18:29
Sadayuki Furuhashi @frsyuki

1000スレッド秒のリソースプールで並列1000 splitまでしか実行できないクエリを1つずつ実行する限り、タスクが待たされることはないから、クエリの終了時間を推定できる。優先順位よりは、リソースプールの属性の方が良いのかな。REALTIMEキュー。

2014-12-18 15:20:18
Sadayuki Furuhashi @frsyuki

作成されているREALTIMEキューの総計分は、クラスタで常に予約しておく必要がある。REALTIMEキュー以外のクエリはovercommitする。するとaccountは、最大Xスレッド秒のREALTIMEリソースプールと、+Yスレッド秒の対話型リソースプールという感じかな。

2014-12-18 15:23:16
Sadayuki Furuhashi @frsyuki

これでクラスタのサイズは Xの総計+Yの総計の30% くらいのルールで決められる。ただこれのスケジューラを理解して使うのはだいぶ大変そうだな…分かりにくい。

2014-12-18 15:25:49
Sadayuki Furuhashi @frsyuki

しかし真面目にやるとこうなりそうだなぁ。まずはREALTIMEリソースプール=X=0とし、対話型リソースプール内でsplitをFIFOかCFQ的にスケジュールするところから始めて、その過程でスケジューラを拡張可能にしておいて、様子を見てREALTIMEリソースプールを足す手順か。

2014-12-18 15:33:58
Sadayuki Furuhashi @frsyuki

aggregation taskを止められない問題については、単純にaggregation taskは必ず開始させて、その分のスレッド秒は常に消費されている換算にすれば良さそう。スケジューリングキューを回避して開始させる。

2014-12-18 15:37:31
Sadayuki Furuhashi @frsyuki

aggregation taskを止められない問題については、単純にaggregation taskは必ず開始させて、その分のスレッド秒は常に消費されている換算にすれば良さそう。スケジューリングキューを回避して開始させる。

2014-12-18 15:37:31
Sadayuki Furuhashi @frsyuki

対話型リソースプールでスレッド秒を制限するとしても、実行中のsplitと上流のtaskが詰まっているなどで原因でブロックしているsplitを、coordinatorから見て区別できないのは問題だな…その辺りの足回りを足さないとスケジューラを書けない。

2014-12-18 15:40:14
Sadayuki Furuhashi @frsyuki

対話型リソースプールでスレッド秒を制限するとしても、実行中のsplitと上流のtaskが詰まっているなどで原因でブロックしているsplitを、coordinatorから見て区別できないのは問題だな…その辺りの足回りを足さないとスケジューラを書けない。

2014-12-18 15:40:14
Sadayuki Furuhashi @frsyuki

まずは区別しなくても良いか。統計情報を平均して何割のsplitがブロックしているかを算出して、その割合だけ割り当てスレッド秒を割り増ししておけばしのげる。平均から外れる特殊クエリを投げられるとおかしくなるけど妥協しておく。妥協できるかは分散がどの程度かによるか…

2014-12-18 15:43:33
Sadayuki Furuhashi @frsyuki

まずは区別しなくても良いか。統計情報を平均して何割のsplitがブロックしているかを算出して、その割合だけ割り当てスレッド秒を割り増ししておけばしのげる。平均から外れる特殊クエリを投げられるとおかしくなるけど妥協しておく。妥協できるかは分散がどの程度かによるか…

2014-12-18 15:43:33
Sadayuki Furuhashi @frsyuki

やはり最初のステップはカスタマイズ可能なsplitのスケジューラを実装するところか。ランダムに割り当てるスケジューラはまず良くない。とりあえずFIFO。JavaならPriorityQueueでCFQにするのは簡単かな。それから統計情報を見つつ、REALTIMEが必要なら足す流れ。

2014-12-18 15:47:23