shinyloadtest の使い方

## はじめに

RでShinyアプリを作っていると、このアプリはスケールするのだろうか？とふと考えたりします。実際、Open SourceのShinyサーバーはRを1プロセスで動かすので、複数人がアクセスしにくると急に動きがもっさりすることがあります。

`shinyloadtest`はRStudio社のパッケージで、自作のShinyアプリに対して、N人同時アクセスをシミュレーションすることができます。例えば、人数を増やしたときに処理性能がどれくらい落ちるかなどをレポート形式で提供してくれます。そのパッケージの使い方を説明します。

## shinyloadtestとは？

私が`shinyloadtest`パッケージの存在を知ったのは、今年のrstudio::conf 2019の"Shiny in Production"のスライドを眺めていて知りました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/69266/d7ff1287-3817-d215-7de6-1c9effa29983.png" width=600>

参考 : [Shiny in Production - P.15](https://speakerdeck.com/jcheng5/shiny-in-production?slide=15)

このパッケージを使うことで、Shinyアプリにアクセスするユーザ数の増加に対して、どの程度スケールするかを見積もることができます。

`shinyloadtest`はRパッケージですが、同時に`shinycannon`というコマンドラインツールを使用します。これは、別途インストールが必要になります。

2019年4月時点では、[Shiny in Production - P.19](https://speakerdeck.com/jcheng5/shiny-in-production?slide=19)で概要を理解することができると思います。より詳細に知りたい場合は、RStudio社の公式ページの[Load Testing Shiny Applications](https://rstudio.github.io/shinyloadtest/)を読むことをおすすめします。また、実践的な使い方については、 Shinyアプリのcranwhalesのスケーリングの評価をどう行ったかを説明している[Case study: Scaling an app](https://rstudio.github.io/shinyloadtest/articles/case-study-scaling.html)を読むことでイメージできるかと思います。

## 使ってみる

以下では、RStudioサーバを使用していることを想定しています。

基本的な流れは、公式ページのとおりですが、
1. `Recording` : Shinyアプリを実際に使用し、その行動ログを保存します。
2. `Run the Load Test` : 保存したログにそって、ワーカーの数（=同時アクセスを想定する人数）を変更しながら、シミュレーションを行います。
3. `Analyze the Results` : シミュレーション結果を分析し、レポートを作成します。

このステップをより細かく行っていきましょう。

### STEP0. Install 

まず、`shinyloadtest`をインストールします。

```R
> devtools::install_github('rstudio/shinyloadtest')
```

また、`shinycannon`を分析する環境に応じてインストールします。

ubuntuの場合は、公式サイトどおりに`$ sudo dpkg -i shinycannon_1.0.0-b267bad_amd64.deb`でインストールできます。

また、後のシミュレーションレポートを生成する際に`pandoc`のバージョンが2.2以上必要になるので、事前にインストールするなど準備しておいてほうが良いかと思います。

### STEP1. Shiny Appをコンソールから立ち上げる

```sh
$ R -e "shiny::runApp('.', port = 6100)"
```

コンソールからRコマンドで起動させることで、Shinyアプリをバックエンドで動かしておきます。ここでは、1st Shiny Contestに投稿した[ShinyAB](https://github.com/okiyuki99/ShinyAB)アプリを動かしています。

参考 : [1st Shiny Contest に参加しました](http://gingi99.hatenablog.com/entry/2019/04/06/232030)

### STEP2. Recordingを行う

```R
> shinyloadtest::record_session("http://127.0.0.1:6100", output_file = "shinyloadtest/recording.log")
Listening on 127.0.0.1:8600
Navigating to: http://127.0.0.1:8600/
Client connected
...
Client disconnected
Stopping server
Server disconnected
```

上記コマンドにより、Shinyアプリがブラウザ上で立ち上がると思います。そこで実際に想定する操作を行いましょう。操作が完了したら、Shinyアプリを閉じます。行った操作は`recording.log`に保存されます。

STEP1でコンソールから起動したShinyアプリは次のSTEPでも使用するため、そのままにしておきましょう。

### STEP3. Replayにより、Recording結果からシミュレーションを行う

ここでは、`shinycannon`コマンドを使用します。いくつかのパラメータを設定する必要があります。

* `--workers` (デフォルト : 1) : N人の同時アクセスを想定したシミュレーションを行う。実際はそれぞれの人を別スレッドとして扱うようです。
* `--loaded-duration-minutes` (デフォルト : 1) : loadtestにかける時間です。ワーカーの立ち上げと停止時間を除いて、この時間の間にレコーディングした操作を繰り返し実行するようです。公式ドキュメントでは、この一つの操作をセッションと呼んでいます。
* `--output-dir` (run) : シミュレーション結果が出力されるディレクトリです。わかりやすい名前をつけましょう。

[Case study: Scaling an app](https://rstudio.github.io/shinyloadtest/articles/case-study-scaling.html)には、1人の場合と、複数人の場合を比較することが望ましいと書いていましたので、ここでは、workersの数を1、5、10、と増やしてそれぞれ5分間のシミュレーションを実行してみます。

```sh
$ shinycannon shinyloadtest/recording.log http://127.0.0.1:6100 --workers 1 --loaded-duration-minutes 5 --output-dir shinyloadtest/run1
$ shinycannon shinyloadtest/recording.log http://127.0.0.1:6100 --workers 5 --loaded-duration-minutes 5 --output-dir shinyloadtest/run5
$ shinycannon shinyloadtest/recording.log http://127.0.0.1:6100 --workers 10 --loaded-duration-minutes 5 --output-dir shinyloadtest/run10
```

### STEP4. Analyze

```R
> df <- shinyloadtest::load_runs("1 workers" = "shinyloadtest/run1", 
                               "5 workers" = "shinyloadtest/run5", 
                               "10 workers" = "shinyloadtest/run10")
> shinyloadtest::shinyloadtest_report(df, output = "shinyloadtest_report.html")
```

`load_runs`コマンドでシミュレーションによって生成されたディレクトリをすべて同時に読み込みデータフレーム化してくれます。データフレームの中身は公式ドキュメントをご確認ください。続いて`shinyloadtest_report`でそのデータからレポート化を行ってくれます。

結果として、以下のような見た目のhtmlレポートが生成されるはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/69266/e1e9ad46-2e12-2667-eb95-a7b70a6a45de.png)


ちなみに`pandoc`2.2以上がないとUpgradeを促される以下のエラーが吐かれるので、適宜環境を用意しましょう。私はMacにPandocをインストールできたので、データだけコピーしてそこで実行しました。

```R
Error: Please upgrade your pandoc version to be at least v2.2
```

参考 : [shinyloadtest using R](https://stackoverflow.com/questions/53832168/shinyloadtest-using-r)


### STEP5. シミュレーション結果レポートを確認する

htmlレポートで6つの項目ごとに分析結果が報告されます。

ここでは、**Session Duration** に着目してみます（というかこれが一番わかりやすい）

#### Session Duration

x軸が時間。y軸が一つのセッションをかかった時間が長いほど下にくるようにスタックさせて表示しています。赤い縦線がRecordingにかかった時間。Recordingが代表的な一セッションを表してるため、この赤い縦線よりも右に長いとよろしくないことがわかります。各色は、Homepage、JS/CSS、Start Session、Calculateのそれぞれの時間を表しているようです。まずは、色別で見るよりも、総時間で見たほうがわかりやすいです。

|  # of workers  |  Session Duration |
| ---- | ---- |
|  1  | ![image.png](https://qiita-image-store.s3.amazonaws.com/0/69266/8744927b-b305-45fb-2fcd-06ef9397fb51.png)|
|  5  | ![image.png](https://qiita-image-store.s3.amazonaws.com/0/69266/174458ca-4e5a-9a2e-35d6-89e14ed44033.png)|
|  10  | ![image.png](https://qiita-image-store.s3.amazonaws.com/0/69266/da43243b-0cfb-e7cc-5537-1f442dc19518.png)|

ワーカー数が1のときに赤い縦線よりもはやくセッションが終わっているように見えます。
シミュレーションの中ではRecordingしたときのダッシュボードを眺めているだけの空き時間？等が再現されてないからなのでしょうか。あまり理由は良くわかってないです。

ワーカー数1の結果を基準に、ワーカー数5と10のときの結果を見比べてみましょう。すると、ワーカー数5のときから約10秒ほどセッション時間が伸び、赤い線を超えはじめました。さらに、ワーカー数10になると最も長くセッションにかかった時間で、ワーカー数1のときと比べて、2倍程度まで伸びてしまうことがわかりました。これだけパフォーマンスが落ちるわけですね。

この結果から、Shinyアプリのパフォーマンスを改善すべきかどうかの定量的な判断ができるようになります。私の場合、Shinyアプリを社内利用することがありますが、「同時アクセスする人数がX人くらいだとすると、Y倍くらい遅延するかもしれませんが、ご理解ください」と言えるだけでも有用そうです。

htmlレポートの残りの5つの項目ですが、お気持ち程度に概要を載せておきます。

#### Sessions
ワーカーごとの処理時間です。これだけ見てもあまりよくわかりませんでした。

#### Event Waterfall

Recordingで取ったイベントごとの時間の推移がわかります。

#### Latency

その名のとおり、セッションごとの遅延時間の棒グラフが表現されます。

#### Event Duration

ワーカーごとに、各イベントにかかった時間を箱ひげ図で可視化されます。

#### Event Concurrency
ワーカーを増やしたときに、どのイベントに時間がかかるようになってるかがわかる。詳細深掘りに使えそうです。

## おわりに

shinyloadtestを使ってみました。実際はこの結果からパフォーマンス改善をすることが決まったら、ボトルネック箇所を洗い出す作業が始まるかと思います。その場合は、HTMLレポートの`Event Duration`等で詳細にボトルネック箇所を特定し、コードの改善をしていく流れかと思います。

最近のRStudio社の資料を見ていると、Shinyをスケーラブルにできるためのパッケージの整備が進んできているように見えます。これからのShiny周りの進展が楽しみです。