SpringBoot 2.5 WebfluxでI/Oが発生するシステムの性能テストしてみた

![springwebflux.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143552/125536df-0ba3-5c46-c5f5-217217bd6797.png)
Spring WebfluxはノンブロッキングI/Oな処理を提供するフレームワークです。
このフレームワークについては、何度か使用したことはありますが性能面にテストしたことがなかったので、軽くテストしてみます。

## テスト結果
先に結果から言っておきます。

スレッドが4つ起動しているSpring上で、40リクエストを同時に実施した場合以下の結果となります


|  手法  |  応答時間(s)  |
| ---- | ---- |
| mvc | 11.041 |
| Webflux | 2.016 |

IOが発生した場合、mvcの場合はスレッドを専有したままIOが終わるのを待ち続けますが、webfluxの場合はIOが発生したら、他のリクエストを待ち受けるようになり、メモリ効率がよくなります。


## Webfluxが解決する課題
こちらのサイトに記載されていることをそのまま引用します。
https://spring.pleiades.io/spring-framework/docs/current/reference/html/web-reactive.html

> Spring WebFlux が作成されたのはなぜですか？
> 答えの一部は、少ないスレッドで同時実行を処理し、より少ないハードウェアリソースで拡張するために、ノンブロッキング Web スタックの必要性です。

つまり、「サーバリソースを節約して、効率よくリクエストをさばくよ」って感じです。
これによってサーバを増設したりなどの余計なコストを削減出来る可能性があります。

## 準備
spring Initializrでアプリケーションを用意します。

https://start.spring.io/

![si.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143552/57210f24-8ba2-031e-173c-43ee22b0ad29.png)

ビルドツールなんでもいいですが、Gradleにしておきました。

DependenciesはWebFluxが入っている「Spring Reactive Web」だけを選択します。

## 実装
### テスト用エンドポイント作成
テスト用のリソースエンドポイントを2つ用意します。

1. 通常のSpring mvc用エンドポイント
  - http://localhost:8080/bench/mvc
2. WebFlux用エンドポイント
  - http://localhost:8080/bench/flux

コントローラはこのように記述しました。

```Java
@RequestMapping("bench")
@RestController
public class TestController {

	@GetMapping("mvc")
	public String webMvc() throws InterruptedException {
		TimeUnit.MILLISECONDS.sleep(1000);
		return "this is mvc";
	}

	@GetMapping("flux")
	public Mono<String> webFlux() {
//		TimeUnit.MILLISECONDS.sleep(1000); これはだめ
		return Mono.just("this is flux").delayElement(Duration.ofMillis(1000));
	}
}
```

mvc、Webflux共にリクエストを受け付けて1秒後に、文字列を返えすような実装にしています。

Webfluxの場合はsleepするとブロッキング処理とみなされ、Webfluxの旨味が享受できないらしく、Monoに対して`.delayElement(Duration.ofMillis(1000)`を呼び出して上げる必要あります。

### ApacheBenchで性能テスト
apachebenchを使って同時にリクエストを投げるようにします。この辺は何でもいいです。

スレッドはどうやら4つ起動するらしい
![スクリーンショット 2021-05-26 14.37.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143552/dcbc5076-6c41-91f0-a271-fd62306f2304.png)

テストではわかりやすく同時40リクエストずつ行っていくこととします。

#### mvc
総リクエスト数40で、同時リクエスト数40でテストします（つまり40リクエストのみ）

```
ab -n 40 -c 40 localhost:8080/bench/mvc
```
想定としては、
4しかスレッドが起動しないので一度にさばけるリクエスト数は4つのみ。
1リクエストあたり1秒間ブロッキングされるので、10秒（40リクエスト/4スレッド*1秒）ほどとなり、10秒くらいで応答するはずです。

```
$ ab -n 40 -c 40 localhost:8080/bench/mvc
Time taken for tests:   11.041 seconds
```
一部結果を省略してますが大体10秒くらい応答しました。予想通りですね。
spring側で1秒間なにかしてるらしく、11秒になってしまいましたが、まあ概ねOKでしょう。

#### Webflux
ではWebfluxはどうでしょうか。
4スレッド立ち上がって、ノンブロッキングで処理するので、各リクエストでIO処理が実行されると他のリクエストを受け付けるようになります。

なので、IO待ちは発生せず1秒くらいで応答するのではないでしょうか？

```
$ ab -n 40 -c 40 localhost:8080/bench/flux
Time taken for tests:   2.016 seconds
```

こちらも概ね予想通りですね。やはり1秒間はなにかの処理に使われているようですが。

### 結果まとめ

リクエスト数を増加させた場合でも比例して、応答時間が長くなります。
※少数以下は省きます

mvc

| リクエスト数 | 応答時間(s)  |
| ---- | ---- |
| 40 | 11 |
| 200 | 51 |
| 400 | 101 |

Webflux

| リクエスト数 | 応答時間(s)  |
| ---- | ---- |
| 40 | 2 |
| 200 | 6 |
| 400 | 11 |

### 使い所とか
Webfluxは同時アクセス数が多いシステムにおいて有利に働きます。トラフィックが上がって来て「捌ききれなくなってきたな」と感じたらmvcからWebfluxに変えてメモリの効率化を求めて行くと良さそうです。そうすれば少ないリソースでより多くの同時リクエストをさばけます。

同時接続数の少ないシステムの場合は必要ないかもしれないですね。



今回はWebfluxについて、スレッドを効率的に扱うためのノンブロッキングな仕組みについて検証しましたが、他にもリアクティブ性を深堀りした内容などがあるようなので、いずれ検証します。

WebfluxにDBが絡むと若干厄介で、通常使うJDBCを使っているとノンブロッキングではなくなるらしいので、そこを改善する仕組みである「R2DBC」を使う必要がでてきます。こちらについてもそのうち記事にしていきます。

WebfluxとJDBCの組み合わせは相性が悪いらしく、DB接続をノンブロッキングに出来ないなら性能が劣化する可能性があるので、安易に使わない方がよいかもしれません。

https://technology.amis.nl/software-development/performance-and-tuning/spring-blocking-vs-non-blocking-r2dbc-vs-jdbc-and-webflux-vs-web-mvc/

間違っていることあれば指摘ください。

### 

# 参考
https://spring.pleiades.io/spring-framework/docs/current/reference/html/web-reactive.html