Elixirで、表題の通り5000万プロセスぐらい作ってみた。
Elixirで20万プロセスを作ったその後
Elixirはerlang VMをベースとした関数型の言語だ。Rubyっぽい文法も使えて便利である。erlangの特徴である、軽量のプロセスが使えるのが一つの売りである。
少し前に、Elixirでプロセス20万位作ってみた という投稿を行った。その時は、デフォルトのprocess_limitのサイズ約26万を超えて、プロセスを指定する方法がわからなかった。親切な方から、26万を超えるprocess_limitの指定方法を教えてもらったので、それならどれくらいまで行けるかと思い、5000万までプロセスを作ってみた。プロセスの単位がインフレしすぎて、実際何の単位なのかわからなくなった。
erlangのプロセス
この場合のプロセスは、erlangで使われる軽量プロセスのことだ。プロセスはerlangの並列処理を形作っており、それぞれ別のCPUでも動作する。プロセスにすることで複数のCPUを効率的に使える。
プログラムの並列度を高める方法として、(通常の)マルチプロセス、マルチスレッド、コルーチン、rubyのfiberなどがあるが、そのような中で、erlangの軽量プロセスがどの程度並列に動くか試してみた。
通常のOSのプロセスを複数立ち上げた場合、大体数百プロセス以上は辛くなりそう。また、スレッドの場合は、数千レベルで辛くなるだろう。一方、erlangの軽量プロセスは数万のオーダーで並列化出来ることが知られている。
実験環境
実験環境として、AWSのm4.10largeを利用した
インスタンスタイプ | CPU | メモリ | OS | erlang | elxir |
---|---|---|---|---|---|
m4.10xlarge | 40 | 160GiB | Ubuntu14.04 | Erlang/OTP 18 | 1.1.1 |
40CPUで160GiBだ。昔、erlangが途中で落ちてコアを吐いたことがあり、その時はメモリいっぱい使っていて、HDDのそのメモリーイメージそのまま吐いたことがあった(うろ覚え)。160GiBのコアを吐く可能性があったのでHDDDは250GB程度用意した。
erlangとElixirのバージョンは以下
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:40:40] [async-threads:10] [kernel-poll:false]
Interactive Elixir (1.1.1) - press Ctrl+C to exit (type h() ENTER for help)
erlangの最大プロセス数の指定
こちらのブログにあるが、+P 1024
などと指定すれば、最大プロセス数を設定できる。
elixirで使う場合には、
export ELIXIR_ERL_OPTIONS="+P 50000000"
と環境変数を設定すれば、最大プロセス数を指定できる。
5000万プロセスを作ってみる。
次のレポジトリに、今回使ったコード一式を用意した。簡単なkey-value storeを5000万個作ってそれぞれに値を入れ引き出す。簡単なプログラムだ。殆どの処理はlib/many_process.ex
に入っている。値の取得を一回しかやらないため、多くのプロセスは一回値の出し入れをした後、休眠状態になる。
git cloneで手に入れた後、mk.sh
を実行
iexが立ち上がった後、次のようにした。
Interactive Elixir (1.1.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(bar@localhost)1> :erlang.system_info(:process_limit)
67108864
iex(bar@localhost)2> iex(bar@localhost)2> ManyProcessFactory.main(["--pnum=50000000"])
のようにすれば良い。
そうすると、ひたすら5000万のプロセスを作る。
5000万プロセスが出来た段階で、大体メモリは130GB位使っていた。
実験結果
elixirを使えば、大体5000万プロセスくらい作れる。ただしメモリーは139GB程度使用する。
その他Tips
バイナリでの実行
当初は、mix escript.build
でプログラムを作って実行していたが、
export ELIXIR_ERL_OPTIONS="+P 50000000"
./many_process --pnum=10000
みたいにして、上手くプロセス数が変更できず、仕方なくiexで立ち上げる方式を採用した。
プロセス数とプロセスリミット
プロセス数は
psize = Enum.count(:erlang.processes)
で取れた。
プロセス数の上限は
plimit = :erlang.system_info(:process_limit)
で取れた。
afterを使った定時実行
afterを使えば、例えば10秒毎に、プロセス数を取得するみたいな構文がかけるが、このやり方がベストプラクティスかはわからない。