App Engine Scaling Config

App Engineは、Deploy時にModule Version毎にスケーリング設定を行うことができます。
結構、細かい値が設定できるのだけど、いまいちどんな作用があるのか分かりづらいものもあります。
この記事では、スケーリング設定の値にどんな作用があるのかを解説します。

Moduleって？という方は [GAE ModulesをSimpleに使う](http://qiita.com/sinmetal/items/d174a0abb578be24e592) を見てください。

# 3つのスケーリング設定概要

App Engineのスケーリング設定は3つ存在します。
それぞれ特性が違うので、Moduleでどのような処理をしたいのかによって、選択するスケーリング設定を変えます。

## Automatic Scaling

Request数に合わせて自動でインスタンスの追加、削除を行う設定。
App Engineでよくオートスケールが特徴と言われるのは、主にこの設定を指している。
たくさんの小さなRequestをさばくのに適している。
End UserからのRequestは大抵短い時間で処理する小さなものが多いので、Default Moduleはこの設定にすることがほとんど。

## Basic Scaling

Request数に合わせて自動でインスタンスの追加、削除を行うが、インスタンスの最大数を事前に決めておく設定。
Request処理可能時間がAutomatic Scalingより長いので、ある程度オートスケールして欲しいバッチ処理などにも適している。

## Manual Scaling

Deploy時に指定した数のインスタンスが常時立ち上がるだけのシンプルな構成。
常時何かを処理したい場合や、インスタンス起動時にマスターデータを読み込みメモリ上に保持したい場合などに利用する。

# Scaling Config

ざっくりと3つのスケーリング設定を見たところで、細かい設定値を見ていきます。
しかし、細かい設定値が何を指しているかを知るためには、まずApp EngineがRequestをどのように処理しているかを知っておく必要があります。

## App Engine Request Processing Flow

App EngineはRequestをインスタンスに割り振る時に、Pending Request Queueと呼ばれるQueueに保持します。
App EngineはPending Request Queueを常に監視していて、インスタンスの処理が追いつかず、QueueにRequestがたまると、新しいインスタンスを追加します。
逆にQueueにRequestがまったく無い状態で、インスタンスが暇になる(Idle状態になる)と、インスタンスを削除します。
これがApp Engineのスケールの仕組みです。

<img width="571" alt="appengine-pending-request-queue.png" src="https://qiita-image-store.s3.amazonaws.com/0/15640/69c96c16-f76e-684b-4591-430d0b88342d.png">

では、これを踏まえて、スケール設定を見ていきます。

## General Setting

どのスケーリング設定でも存在する設定値として、instance_classがあります。
これはCPUとメモリに関連する設定です。
FとBが存在し、それぞれFrontend, Backendの略です。
FとBにほとんど違いはありませんが、BにだけCPU Limitが大きいB8が存在します。
Automatic ScalingはFを指定し、Basic Scaling, Manual ScalingはBを指定します。
課金はFrontend Instance HourとBackend Instance Hourに分かれています。
Dialy無料枠はF1:28H, B1:8Hなので、F2やB2などの上のinstance_classを使うと、無料枠の減少が早くなります。
必要最低限のinstance_classを指定すると安くていいです。

|Instance Class|Memory Limit|CPU Limit|Cost per Hour per Instance|
|---|---|---|---|
|B1|128 MB|600 Mhz|$0.05|
|B2|256 MB|1.2 Ghz|$0.10|
|B4|512 MB|2.4 Ghz|$0.20|
|B4_1G|1024 MB|2.4 Ghz|$0.30|
|B8|1024 MB|4.8 Ghz|$0.40|
|F1|128 MB|600 Mhz|$0.05|
|F2|256 MB|1.2 Ghz|$0.10|
|F4|512 MB|2.4 Ghz|$0.20|
|F4_1G|1024 MB|2.4 Ghz|$0.30|

## Automatic Scaling

Automatic Scalingに設定する値は9つあります。
3つのスケール設定の中で最もややこしいものです。

```
instance_class: F1
automatic_scaling:
  target_cpu_utilization: 0.6 # default value
  target_throughput_utilization: 0.6 # default value
  max_instances: 5
  min_instances: 5
  max_idle_instances: automatic  # default value
  min_idle_instances: 3
  min_pending_latency: 30ms  # default value
  max_pending_latency: automatic
  max_concurrent_requests: 80
```

### target_cpu_utilization

CPU利用率のしきい値で、0.5〜0.95の値を指定できる。デフォルトは 0.6
この値を超えると新しいインスタンスを作成するので、料金を安くしたい場合は高い値を設定し、パフォーマンスを重視する場合は低い値を設定する。

#### sinmetal 所感

エンタープライズではとりあえずデフォルト値、個人で安くしたい場合は、0.75ぐらいを設定すればよさそう

### target_throughput_utilization

max_concurrent_requestsとともに使用して、同時リクエスト数によって新しいインスタンスが作成されるタイミングを指定する。 同時リクエスト数がmax_concurrent_requests×target_throughput_utilizationに等しい値に達すると、新しいインスタンスを作成する。
0.5〜0.95の値を指定でき、デフォルトは 0.6

#### sinmetal 所感

エンタープライズではとりあえずデフォルト値、個人で安くしたい場合は、0.75ぐらいでよさそう

### max_instances

最大インスタンス数を 0 ~ 2147483647 の間で指定する。0を指定すると設定が無効化されるので、最大値を設定したのとだいたい同じになる。

#### sinmetal 所感

incstance_classやmax_concurrent_request, また、どのような処理を行うのかと予算との相談で値を設定することになります。
個人で作ってる適当なアプリケーションでお金を節約したいなら、1 ~ 5 ぐらいを設定すればよさそう

### min_instances

リクエストが来なくても、常時立ち上げておく最低台数。
トラフィックが割り当てられていないVersionでも、min_instancesで指定した台数のインスタンスが常に立ち上がるので、注意。
Min Instanceが必要なくなったVersionは、 [App Engine Admin API appengine.apps.services.versions.patch](https://developers.google.com/apis-explorer/#search/admin/appengine/v1/appengine.apps.services.versions.patch?appsId=gcpugjp-dev&servicesId=default&versionsId=m1-i0&updateMask=automaticScaling.standardSchedulerSettings.minInstances&_h=4&resource=%257B%250A++%2522automaticScaling%2522%253A+%250A++%257B%250A++++%2522standardSchedulerSettings%2522%253A+%250A++++%257B%250A++++++%2522minInstances%2522%253A+0%250A++++%257D%250A++%257D%250A%257D&
) を利用して、Min Instanceを0に指定すれば、最低台数のインスタンスがいなくなる。

#### sinmetal 所感

個人のProjectでは課金額の節約のために大抵0にしている。
Javaで重厚なフレームワークを使っていて、Spin upが遅い時などは、1~2あるとお金を犠牲にパフォーマンスを上げれるかも。

### max_idle_instances

Idle状態で待機していてよいDynamic Instanceの最大数
例えば、Requestのスパイクが発生し、50台のインスタンスが立ち上がった後、max_idle_instanceの値が10だと、スパイクが落ち着いて暇になったら、なるべく早く10台までDynamic Instaceを減らす。
大きな値を設定しておくと、スパイクが小刻みに発生する時などに、パフォーマンスが安定する。

#### sinmetal 所感

個人のProjectでは課金額の節約のために0 ~ 2にしている。
GoようにSpin upが40msだと、Idleで待機しているインスタンスが必要ないので、とても小さな値で問題無さそう。
Javaの場合だと、1~3ぐらいにしておくとお金を犠牲にパフォーマンスを上げれるかも。

### min_idle_instances

Idle状態で予約しておくインスタンスの数
[warmup request](https://cloud.google.com/appengine/docs/standard/go/warmup-requests/) と一緒に利用する。
Requestがまったく無い状態でも後ろで待機しているため、課金額が上がるが、突然のトラフィック増加に対応しやすくなる。
min_idle_instancesの設定値によって起動されるインスタンスは、Consoleには表示されない。

#### sinmetal 所感

個人のProjectでは課金額の節約のために大抵0にしている。
Instanceの立ち上がりの補助のための設定なので、GoのようにSpin upが40msだと、あんまり意味がない。
Javaで重厚なフレームワークを使っていて、Spin upが遅い時などは、1~2あるとお金を犠牲にパフォーマンスを上げれるかも。

### min_pending_latency

Pending Request Queueの中で、既存のインスタンスが空くのを待つ最小時間。
大きな値を設定すると、既存のインスタンスが空くのをなるべく待つようになるので、新しいインスタンスが生成されにくくなる。

#### sinmetal 所感

僕はGoの場合はだいたいautomaticのまま。
Javaの場合は、3sec以上spin upにかかる場合が多いので、min_pending_latencyも3secにしておいて、既存のインスタンスが空くのを待つようにしたほうが、課金額が節約できて、パフォーマンスも良くなるかも。

### max_pending_latency

Pending Request Queueの中で、待てる最大時間
RequestがPending Request Queueの中での待ち時間が、この値を超えると、新しいインスタンスの生成を要求する。
大きな値を設定すると、Pending Request Queueの中で待つ時間が長くなるので、新しいインスタンスが生成されにくくなる。

#### sinmetal 所感

僕はGoの場合はだいたいautomaticのまま。
Javaの場合は、3sec以上spin upにかかる場合が多いので、max_pending_latencyも5secぐらいにしておいて、既存のインスタンスが空くのを待つようにしたほうが、課金額が節約できて、パフォーマンスも良くなるかも。

### max_concurrent_requests

1インスタンスで、同時に処理して良いリクエスト数。

#### sinmetal 所感

instance_classとの兼ね合いもあるけど、それなりに大きな値をしても良さそう。
Defaultは8だけど、ほとんどのWeb Applicationが80ぐらいの値にしても問題無さそう。

### 制約

#### Deadlines

App Engineは1つのRequestを処理できる時間に制限があり、Frontend Instanceは短め。
この時間を超えると、強制的にRequestが終了され、Errorが返される。

* 通常のRequest 60sec
* TaskQueue or CronからのRequest 10min

### min_instancesとmin_idle_instancesを同時に指定

min_instnacesの数、インスタンスが常時起動された状態となり、かつmin_idle_instancesの数、予約された状態となる。
以下は min_instances:5, min_idle_instances:3を指定した場合のConsoleの表示。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/15640/e5e7b48f-da49-4e73-77cc-11f5a8d87e51.png)

インスタンスが5つ立ち上がった状態だが、Billed Instance Estimateは8となっている。後ろで3つInstanceが予約されているのではないかと思う。
ただ、この状態でリクエストが全く来ない状態がしばらく続くと、Billed Instance Estimateが5に下がる。
暇なので、予約済みのインスタンスを解放したりしてそう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/15640/5ef273b2-6d23-f8dd-cb4a-b087da3deeed.png)

## Basic Scaling

Basic Scalingで設定する値は2つです。

```
instance_class: B1
basic_scaling:
  max_instances: 11
  idle_timeout: 10m
```

### max_instances

スケール時の最大インスタンス数

#### sinmetal 所感

コストとパフォーマンスのトレードオフで好きに決めればいいんじゃないかと思う。

### idle_timeout

Idle状態になってから、シャットダウンされるまでの時間
defaultは5min

#### sinmetal 所感

defaultはちょっと長めになっているので、1~2min程度にしておくのが良さそう。
Javaで、Spin upが遅くて困る場合は、defaultでもいいかも。

### 制約

#### Deadlines

24h

Backend Instanceはバッチ処理などを行うことも想定されているため、Deadlineがかなり長めに設定されている。
それでも、途中でエラーになる可能性はあるため、10minぐらいで区切りながら、処理を進めていくほうが無難。

## Manual Scaling

設定はinstance数の1つだけで最も分かりやすい。

```
instance_class: B1
manual_scaling:
  instances: 5
```

### instances

起動するインスタンスの数

### 制約

#### Deadlines

24h

Manual Scalingは指定した数のインスタンスが常に立ちあげられている状態だけど、シャットダウンされないわけではない。
無限ループで、延々と処理をし続けるとかはできないので、注意。

# Refs

* [Google App Engine Standard for Go Scaling Elements](https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=en#scaling_elements)
* [Increase performance while reducing costs with the new App Engine scheduler](https://cloudplatform.googleblog.com/2018/05/Increase-performance-while-reducing-costs-with-the-new-App-Engine-scheduler.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+ClPlBl+%28Cloud+Platform+Blog%29)
* [App Engine’s new scheduler settings](https://medium.com/@duhroach/app-engines-new-scheduler-settings-a8a34a1961b2)