Resource Group in Presto

この記事は[Distributed computing Advent Calendar](http://qiita.com/advent-calendar/2016/distributed-computing)16日目の記事です。

PrestoはFacebookがメインとなって開発しているオンメモリでの処理を特徴とする分散クエリエンジンです。各タスクの中間データをメモリにのせたまま処理するためHiveなどと比べて高速に大きなデータに対して処理を行うことができます。Prestoは現在でも活発に開発が進められており最新のバージョンは[0.160](https://prestodb.io/docs/current/release/release-0.160.html)となります。この2ヶ月程でもminor versionが5つくらい出ているので安定バージョンがなかなか得られない代わりにbug fixやコミュニティからのfeedback迅速に取り入れられています。

今回はまだexperimentalなものではありますが、新しい機能であるPrestoのResource Groupについて書きたいと思います。

# Resource Groupとは

Resource Groupはグループやアカウント毎に利用できるリソースに制限をかけることのできる機能です。これまでもPrestoでは`query.max-memory`などでクエリ毎、node毎に利用できるメモリは制限できたのですが、アカウントやsource propertyに従って柔軟に制限を設定することは簡単ではありませんでした。またアカウントの同時実行数などもかけることはできませんでした。これらをPrestoの機能として提供するものがResource Groupです。詳細は[Resource Group Configuration](https://prestodb.io/docs/current/admin/resource-groups.html)に記載されています。

# Resource Groupの使い方

設定は形式はJSONファイルで下記の用に記載します。このファイルの場所は`resource-groups.properties`内の`resource-groups.config-file` propertyで指定します。

```json
{
  "rootGroups": [
    {
      "name": "global",
      "softMemoryLimit": "80%",
      "maxRunning": 100,
      "maxQueued": 1000,
      "schedulingPolicy": "weighted",
      "jmxExport": true,
      "subGroups": [
        {
          "name": "adhoc_${USER}",
          "softMemoryLimit": "10%",
          "maxRunning": 2,
          "maxQueued": 1,
          "schedulingWeight": 9,
          "schedulingPolicy": "query_priority"
        },
        {
          "name": "pipeline",
          "softMemoryLimit": "20%",
          "maxRunning": 5,
          "maxQueued": 100,
          "schedulingWeight": 1,
          "jmxExport": true,
          "subGroups": [
            {
              "name": "pipeline_${USER}",
              "softMemoryLimit": "10%",
              "maxRunning": 1,
              "maxQueued": 100,
              "schedulingPolicy": "query_priority"
            }
          ]
        }
      ]
    },
    {
      "name": "admin",
      "softMemoryLimit": "100%",
      "maxRunning": 100,
      "maxQueued": 100,
      "schedulingPolicy": "query_priority",
      "jmxExport": true
    }
  ],
  "selectors": [
    {
      "user": "bob",
      "group": "admin"
    },
    {
      "source": ".*pipeline.*",
      "group": "global.pipeline.pipeline_${USER}"
    },
    {
      "group": "global.adhoc_${USER}"
    }
  ],
  "cpuQuotaPeriod": "1h"
}
```

まず大きくわけて3つの項目があります。

- `rootGroups`: Resource Groupは上記のように入れ子に記述することができ、すべてのResource Groupはこの`rootGroups`の下に置かれます。
- `selectors`: すべてのクエリはどれかのResource Groupに関連づけられますが、このときのマッチングルールを記載するのがこの`selectors`です。詳細は後述します。
- `cpuQuotaPeriod`: CPUに関する制限を計算するときの基準となる値です。詳細は後述します。

ひとつずつ見ていきます。

## rootGroups

各Resource Groupは入れ子にすることができ、以下の要素を持ちます。

|名前|意味|
|:----|:----|
|`name`|Resource Groupにつける一意な名前。selectorで使う|
|`maxQueued`|queued状態のクエリの最大数|
|`maxRunning`|running状態のクエリの最大数|
|`softMemoryLimit`|利用できる最大メモリ。容量(e.g. `10GB`)かクラスタ全体からの割合(e.g. `10%`)で指定。これを超えてからsubmitされたクエリはqueued状態になる。|
|`softCpuLimit`|`cpuQuotaPeriod`で指定された時間内に利用できる最大CPU時間。これを超えると実行可能な最大クエリ数が減らされる|
|`hardCpuLimit`|`cpuQuotaPeriod`に利用できる最大CPU時間。これを超えると新しく入ってきたクエリはqueued状態になる。|
|`schedulingPolicy`|queued状態のクエリがどのように実行されるかを指定するパラメタ。`fair`, `weighted`,`query_priority`の3種類がある|
|`schedulingWeight`|同階層のサブグループと比較した場合も重み|
|`jmxExport`|JMXでResource Groupのメトリクスを出すかどうか|
|`subGroups`|サブグループのリスト|

`schedulingPolicy`で指定されるpolicyそれぞれは

- `fair`: FIFO.クエリ、サブグループともに早いもの勝ちで処理されていく
- `weighted`: `query_priority`に比例した形で確率的に次のクエリ、サブグループが選ばれていく
- `query_priority`: 確率的にではなく`query_priority`が大きい順に厳密に次のクエリ、サブグループが選ばれていく

となります。クエリが所属するgroupはleafとなっているgroup、つまり`subGroups`を持たないものになります。またある設定値が`subGroups`がoverrideされてない場合は親の設定値が使われます。

## selectors
submitされたクエリはselectorによって所属するgroupを決められます。selectorが持てる設定値は下記の３つです。

- `user`: session propertyのuserにマッチするような正規表現
- `source`: session propertyのsourceにマッチするような正規表現
- `group`: マッチしたクエリが所属するgroup名

つまりselectorはクエリが所属するgroupを決めるためのマッチングロジックを記述するためのものになります。マッチするものが複数ある場合には先頭のselectorが優先されます。

ちなみに内部的にはselectorは[`SelectionContext`](https://github.com/prestodb/presto/blob/master/presto-spi/src/main/java/com/facebook/presto/spi/resourceGroups/SelectionContext.java#L27)というクラスタが担っているのですが、これらに加えて認証情報とクエリの優先度も持っているので将来的にこれらでgroupをマッチングさせることもできるかもしれません。


# 動的なResource Group

設定ファイルは管理者が生成するファイルですが、ユーザや部署毎に異なるResource Groupを作りたい場合もあると思います。この場合同じような設定を並べると、いちいち設定をする必要があり面倒です。実は上記の例でお気づきかもしれませんが、Resource Groupの名前、selectorにはplace holder的に変数を使うことができます。例えば各account毎にscheduledとadhocクエリを使い分けたい場合。下記のように書くことで新規ユーザがクエリを投げた場合でもsourceを指定することでadhoc, scheduledの両Resource Groupを利用することができます。対応するResource Groupへの初めてのクエリの場合には自動的に作成されます。

```json
{
  "rootGroups": [
    {
      "name": "global",
      "schedulingPolicy": "weighted",
      "subGroups": [
        {
          "name": "adhoc_${USER}",
          //
        },
        {
          "name": "scheduled_${USER}",
          //
        }
      ]
    }
  ],
  "selectors": [
    {
      "source": "scheduled",
      "group": "global.scheduled_${USER}"
    },
    {
      "source": "adhoc",
      "group": "global.adhoc_${USER}"
    }
  ]
}
```

このplace holderに利用できる変数はselectorで利用する`${USER}`と`${SOURCE}`の2種類があります。

# Last but not least

大事なことですが、Resource Groupはまだ**experimental**な機能として提供されています。そのためdefaultでは有効になっておらず利用するためには下記のpropertyをONにする必要があります。

```
experimental.resource-groups-enabled=true
```

私が所属する[Treasure Data](http://www.treasuredata.com/)でも現在このResource Groupの評価、試験を行っており細かいリソース制限が簡単にかけられることを確認しています。是非試してみてください。



Learn more in [Resource Groups](https://prestodb.io/docs/current/admin/resource-groups.html)