Elixirで遠隔PCに侵入#5風「SV経由で10万プロセス起動した際の負荷」

[前回](http://qiita.com/piacere/items/d06e46b69f47ff349de5) からの続きで、スーパーバイザ（以下SVと省略）経由の大量GenServer起動における負荷を確認します :headphones:

なお、検証に使ったマシンのスペックは、CPUがIntel Core i5-2540M 2.60GHz、メモリ8GByteと、3Dゲームがギリギリ遊べるかどうかのロースペックな旧型PCを使っています

# SVからの複数GenServer起動

SVから、任意のプロセス数のGenServerを起動できるよう、以下のような変更を入れます（起動にかかる時間も測定できるようにしておきます）

```elixir:lib/pass_supervisor.ex
import Supervisor.Spec

defmodule PassMultipleSupervisor do
	def start_link( processes ) do
		start = Timex.now()
		servers = 1..processes |> Enum.map( &( worker( PassGenServer, [], id: &1 ) ) )
		Supervisor.start_link( servers, strategy: :one_for_one )
		IO.puts( "Spent Milliseconds=#{Timex.diff( Timex.now(), start, :milliseconds )}" )
	end

	def cat( parameter, name \\ "0" ) do
		GenServer.call( :global.whereis_name( name ), { :cat, parameter } )
	end

	def pwd( name \\ "0" ) do
		GenServer.call( :global.whereis_name( name ), :pwd )
	end
end
```

GenServer側もPIDが確認できるよう変更します

```elixir:lib/pass_genserver.ex
defmodule PassGenServer do
	use GenServer

	def start_link() do
		{ :ok, pid } = GenServer.start_link( __MODULE__, "" )
		IO.puts( "--- PassGenServer.start_link() PID=#{inspect pid} ---" )
		{ :ok, pid }
	end

	def handle_call( :pwd, _from, _state ) do
		{ :ok, result } = File.cwd()
		{ :reply, result, "" }
	end

	def handle_call( { :cat, path }, _from, _state ) do
		{ :ok, result } = File.read( path )
		{ :reply, result, "" }
	end
end
```

次に、プロセス起動の確認ですが、その前に、Observerで総プロセス数を確認しておきます

```
iex> :observer.start
```

プロセス起動前は、107プロセス起動済みであることが確認できます（iexを起動してしばらくは、109プロセスになっていますが、15秒程度すると107に落ち着きます）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/9088cac1-05ff-c8e0-1980-7ca890b4ca3b.png)

今回、頻繁にプロセス数の変化を見るため、[View]-[Refresh Interval]にて、10秒となっている設定を、1秒に変更しておきます

最初は、プロセス数5個程度で、お試しに正常起動できることの確認をします

```
iex> PassSupervisor.start_link( 5 )
--- PassGenServer.start_link() PID=#PID<0.212.0> ---
--- PassGenServer.start_link() PID=#PID<0.213.0> ---
--- PassGenServer.start_link() PID=#PID<0.214.0> ---
--- PassGenServer.start_link() PID=#PID<0.215.0> ---
--- PassGenServer.start_link() PID=#PID<0.216.0> ---
Spent Milliseconds=89
{:ok, #PID<0.211.0>}
```

5個のGenServerのPIDと、1個のスーパーバイザのPIDが発行されました

Observerのプロセス数も、6個増えていることが確認できます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/d4582c7b-d920-8cd4-5cd1-a12a8cc47157.png)

さて、ここから多数のプロセスを起動してみますが、その前に、GenServer起動時のPID表示で重くなってしまうのを避けるために、コメントアウトしておきます

```elixir:lib/pass_genserver.ex
defmodule PassGenServer do
	use GenServer

	def start_link() do
		{ :ok, pid } = GenServer.start_link( __MODULE__, "" )
#		IO.puts( "--- PassGenServer.start_link() PID=#{inspect pid} ---" )
		{ :ok, pid }
	end
　…
```

まず100+1個追加を試してみます

```
iex> recompile()
iex> PassSupervisor.start_link( 100 )
Spent Milliseconds=13
{:ok, #PID<0.219.0>}
```

13ミリ秒と、一瞬ですね :dancer_tone1:

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/74787a2d-0cf2-975c-20e0-70eaca7eda17.png)

再度、100+1個、追加してみても、起動速度が劣化しないことを確認します

```
iex> PassSupervisor.start_link( 100 )
Spent Milliseconds=12
{:ok, #PID<0.335.0>}
```

全く変わりませんでした :kissing_smiling_eyes:

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/641aabbe-f0e8-2853-7e01-45290cfbbbe4.png)

100個程度のサーバプロセスであれば、一瞬で起動できる、ということが分かりました :pick:

# SV経由での大量のGenServer起動時の負荷

では、一気に10,000個を追加してみるとしましょう :laughing:

```
iex> PassSupervisor.start_link( 10000 )
Spent Milliseconds=1744
{:ok, #PID<0.439.0>}
```

ナント、1.7秒程度です :scream:

Observerで確認しても、10,000個ちゃんと追加されていることが確認できます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/22540f0f-e5ba-9348-5464-a826c339c857.png)

Observerの「Processes」タブからPIDで見ても、101+101+10001個のGenServerが存在することが確認できます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/ff79c429-d586-774a-df06-32b988ddccf1.png)

更に数回、10,000個ずつを上乗せしても、劣化しないか確認してみます :thinking:

```
iex> PassSupervisor.start_link( 10000 )
Spent Milliseconds=1772
{:ok, #PID<0.30406.0>}
iex> PassSupervisor.start_link( 10000 )
Spent Milliseconds=1629
{:ok, #PID<0.8555.1>}

　…6回ほど繰り返す…

iex> PassSupervisor.start_link( 10000 )
Spent Milliseconds=1831
{:ok, #PID<0.17225.3>}
```

ほぼ劣化していないことが確認できました、凄まじいです :tada:

そして気付けば、10万個を超えるGenServerが起動済み、ということになります

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/925246dc-2b66-c41e-cf7a-61b54bfb814d.png)

この状態でも、マシンの通常利用も特に遅くなることも無く、iex自体もレスポンスが鈍くなるといったこともありません

また、最初の方や、途中で起動したGenServerを呼び出すと遅くなる、といった現象も出ないし、GenServer自体の反応もクイックなままです

```
iex> GenServer.call( pid( 0, 212, 0 ), :pwd )
"/code/pass"
iex> GenServer.call( pid( 0, 10435, 0 ), :pwd )
"/code/pass"
```

今回の検証に使ったマシンは、ロースペックな旧型PCにも関わらず、大量のプロセス起動の負荷をほとんど感じなかったため、もっとハイスペックなマシンであれば、より迅速かつ大量にプロセスをハンドリングできる可能性は高いです :blush:

# 参考：大量プロセス起動時のPIDの推移

ちなみに、発行されたPID（途中、SVのPIDも含んでいる）は、以下のレンジとなっていました（途中、飛び番がある）

2桁目の数字が32,767を超えると、3桁目がインクリメントされるルールのようです

- <0.212.0>～<0.32767.0>
- 　<0.0.1>～<0.32767.1>
- 　<0.0.2>～<0.32767.2>
- 　<0.0.3>～<0.27225.3>

# 予習：プロセスダウン時のSVによる再起動後のPID

さて、プロセスダウン時にSVで再起動がかかったプロセスのPIDがどうなるかを確認しておきましょう

```
iex> Process.exit( pid( 0, 212, 0 ), :kill )
true
```

Observerの「Processes」で確認すると、<0, 212, 0>が消え、新たに<0, 10419, 13>というプロセスが起動されていることを確認しました

このため、一斉にプロセスをダウンさせると、元のPIDから、別のレンジでPIDが発行され直すことが想定されます