前回 からの続きで、スーパーバイザ(以下SVと省略)経由の大量GenServer起動における負荷を確認します
なお、検証に使ったマシンのスペックは、CPUがIntel Core i5-2540M 2.60GHz、メモリ8GByteと、3Dゲームがギリギリ遊べるかどうかのロースペックな旧型PCを使っています
SVからの複数GenServer起動
SVから、任意のプロセス数のGenServerを起動できるよう、以下のような変更を入れます(起動にかかる時間も測定できるようにしておきます)
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が確認できるよう変更します
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に落ち着きます)
今回、頻繁にプロセス数の変化を見るため、[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個増えていることが確認できます
さて、ここから多数のプロセスを起動してみますが、その前に、GenServer起動時のPID表示で重くなってしまうのを避けるために、コメントアウトしておきます
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ミリ秒と、一瞬ですね
再度、100+1個、追加してみても、起動速度が劣化しないことを確認します
iex> PassSupervisor.start_link( 100 )
Spent Milliseconds=12
{:ok, #PID<0.335.0>}
全く変わりませんでした
100個程度のサーバプロセスであれば、一瞬で起動できる、ということが分かりました
SV経由での大量のGenServer起動時の負荷
では、一気に10,000個を追加してみるとしましょう
iex> PassSupervisor.start_link( 10000 )
Spent Milliseconds=1744
{:ok, #PID<0.439.0>}
ナント、1.7秒程度です
Observerで確認しても、10,000個ちゃんと追加されていることが確認できます
Observerの「Processes」タブからPIDで見ても、101+101+10001個のGenServerが存在することが確認できます
更に数回、10,000個ずつを上乗せしても、劣化しないか確認してみます
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>}
ほぼ劣化していないことが確認できました、凄まじいです
そして気付けば、10万個を超えるGenServerが起動済み、ということになります
この状態でも、マシンの通常利用も特に遅くなることも無く、iex自体もレスポンスが鈍くなるといったこともありません
また、最初の方や、途中で起動したGenServerを呼び出すと遅くなる、といった現象も出ないし、GenServer自体の反応もクイックなままです
iex> GenServer.call( pid( 0, 212, 0 ), :pwd )
"/code/pass"
iex> GenServer.call( pid( 0, 10435, 0 ), :pwd )
"/code/pass"
今回の検証に使ったマシンは、ロースペックな旧型PCにも関わらず、大量のプロセス起動の負荷をほとんど感じなかったため、もっとハイスペックなマシンであれば、より迅速かつ大量にプロセスをハンドリングできる可能性は高いです
参考:大量プロセス起動時の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が発行され直すことが想定されます