園児ニア日記

Plamo対応、多言語対応、高性能Webサーバー、MonsterEngineをリリースしました

MonsterEngine

MonsterEngineとは

MonsterEnginePlamoに対応した多言語対応の高性能Webサーバーです。
Rubyで言うところのPumaみたいなやつです。
公式サイトは https://monsterengine.org
公式サイトのデザインは@kanaktに、英文の添削は@matsumoto4aにお願いしました。
本当はPlamoのリリース時に一緒に出したかったのですが、思った以上に時間がかかってしまい別々にリリースすることになりました。
実はRuby版にはいくつかバグがあるのですが、数日かけても直せなかったため一旦バグったままリリースして、後ほどコミュニティなどで質問してみることにしました。

Ruby版のバグ

Ruby版は現在以下の2つのバグがあります(ただこの2つのバグはRuby版のMonsterEngineではなくRuby版のPlamoのバグの可能性があります)

  • GCを無効化しないとセグフォで落ちる
  • 同時にアクセスがあると稀にハングアップしてしまう

パフォーマンス

MonsterEngineよりPumaのほうが多機能だったりして単純な比較はできないですが、簡単なベンチマークをとってみました。
Ruby版にバグがあるためGCを無効化、h2loadのクライアント数を1に設定してベンチマークを5回とって一番結果の良かったものが以下です(条件を同じにするためPumaの方もGCを無効化しています)

$ # Puma(ruby: 2.7.0, puma: 4.3.1)デフォルトだと標準出力にログが出力されるのでログ出力を無効化しています
$ h2load -c 1 -n 10000 -p http/1.1 http://localhost:8888
starting benchmark...
spawning thread #0: 1 total client(s). 10000 total requests
Application protocol: http/1.1
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 2.96s, 3378.32 req/s, 293.62KB/s
requests: 10000 total, 10000 started, 10000 done, 10000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 10000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 869.14KB (890000) total, 439.45KB (450000) headers (space savings 0.00%), 68.36KB (70000) data
                     min         max         mean         sd        +/- sd
time for request:      235us      1.10ms       295us        31us    81.98%
time for connect:      104us       104us       104us         0us   100.00%
time to 1st byte:      746us       746us       746us         0us   100.00%
req/s           :    3378.56     3378.56     3378.56        0.00   100.00%
$ # Ruby版MonsterEngine(ruby: 2.7.0, monster_engine: 0.1.0)
$ h2load -c 1 -n 10000 -p http/1.1 http://localhost:8888
starting benchmark...
spawning thread #0: 1 total client(s). 10000 total requests
Application protocol: http/1.1
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 447.16ms, 22363.51 req/s, 1.75MB/s
requests: 10000 total, 10000 started, 10000 done, 10000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 10000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 800.78KB (820000) total, 468.75KB (480000) headers (space savings 0.00%), 68.36KB (70000) data
                     min         max         mean         sd        +/- sd
time for request:       34us       464us        44us        10us    94.05%
time for connect:       98us        98us        98us         0us   100.00%
time to 1st byte:      569us       569us       569us         0us   100.00%
req/s           :   22370.88    22370.88    22370.88        0.00   100.00%
$ # Rust版MonsterEngine(rust: 1.40.0, libmonsterengine: 0.1.0)libmonsterengineを直接Rustから使ってる
$ h2load -c 1 -n 10000 -p http/1.1 http://localhost:8888
starting benchmark...
spawning thread #0: 1 total client(s). 10000 total requests
Application protocol: http/1.1
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 227.54ms, 43947.54 req/s, 3.44MB/s
requests: 10000 total, 10000 started, 10000 done, 10000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 10000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 800.78KB (820000) total, 468.75KB (480000) headers (space savings 0.00%), 68.36KB (70000) data
                     min         max         mean         sd        +/- sd
time for request:       19us       163us        22us         6us    91.61%
time for connect:      104us       104us       104us         0us   100.00%
time to 1st byte:      273us       273us       273us         0us   100.00%
req/s           :   43976.26    43976.26    43976.26        0.00   100.00%

機能が違う上にバグの関係でGC無効化やクライアント数制限などをしているので単純な比較はできませんが、PumaとRuby版MonsterEngineだとRuby版MonsterEngineのほうが6倍以上高速なようです。
Ruby版MonsterEngineとRust版MonsterEngineで思っていたほど差がないのには驚きました。

ToDo

未実装の機能の中で大事そうなものだとGraceful Restartがあります。
これは次のアップデートで入れられたらと思っています。

お試しDocker

現状Linuxでしか動かないため環境構築が面倒だと思うのでRuby版のお試し用Dockerfileを用意しました(ベンチマークを取る場合は条件を合わせるためMonsterEngineじゃない方もDockerで動かしてください)

  1. 以下をコピーしてファイル名 Dockerfile で保存
  2. $ docker build -t monster_engine_example . でDockerイメージを作成
  3. $ docker run --rm -it -d -p 8888:8888 monster_engine_example
  4. ポート8888にリクエスト(例: $ curl 'localhost:8888'
FROM archlinux:20200106
ENV LANG C.UTF-8
RUN pacman -Sy --noconfirm ruby ruby-bundler rust make gcc git
RUN git clone https://github.com/plamo/libplamo.git /usr/local/src/libplamo
RUN git clone https://github.com/monsterengine/libmonsterengine.git /usr/local/src/libmonsterengine
RUN cd /usr/local/src/libplamo && make && make install
RUN cd /usr/local/src/libmonsterengine && make && make install
RUN git clone https://github.com/monsterengine/monster_engine_ruby.git /usr/local/src/monster_engine_ruby
RUN cd /usr/local/src/monster_engine_ruby && bundle install && bundle exec rake build
WORKDIR /usr/local/src/monster_engine_ruby
EXPOSE 8888
CMD ["./bin/monster_engine"]

最後に

Plamoだけだとただの抽象化レイヤーでしかないので今までは試すことも難しかったと思いますが、今回MonsterEngineをリリースしたことで手軽にPlamoを使えるようになったので、ぜひ一度試してみてください!🙇