最新のNGINXはアプリケーションサーバー!?NGINX UnitのベンチマークをPHP,Python,Goで計測してみた!!

  • 27
    Like
  • 3
    Comment

NGINXって??

image.png

簡単にNGINXの特徴について説明します.

  • イベント駆動のWebサーバー
  • 静的コンテンツの配信が得意
  • リバースプロキシとして使われることも多い
  • 全アクティブサイトの中で2番目に多く使われている(19.60%)

参考:wikipedia nginx

以前(といってもだいぶ前ですが)はApacheが一強のサーバー業界でした.私もあんまり詳しくないですが,サーバーが安くなる中,C10K問題というものが業界で話題になり,Apacheが採用していたpre-Fork型のアーキテクチャのサーバーでは大量のリクエストが処理できなくなってきました.その中で,イベント駆動型のアーキテクチャのNGINXが大量のリクエストを処理することが可能で,注目を浴びたようです.
そこで,Webサービスのリクエストの一番最初をNGINXが受け,リバースプロキシをする.という構成をよく取られるようです.また,メディアなどのWebサイトは高速にレスポンスさせる必要があり,またコンテンツとして静的なものが多い特性もあるので,NGINXの採用をすることもあるようです.

NGINX-Unitって??

NGINX-Unitは2017年9月8日に新たに発表されたプロダクトです.その特徴としては,

  • NGINX Unitはアプリケーションサーバー
  • PHP,Python,Goに対応
  • RESTful APIやJSONによってサーバーの設定がリアルタイムに動的に変更可能

引用:NGINXからアプリケーションサーバ「NGINX Unit」がオープンソースで登場。PHP、Go、Pythonに対応。Java、Node.jsにも対応予定

という特徴があるようです.
一般的に,Webサービスは三層のアーキテクチャにすることが多いようです.
image.png

単純な静的なHTMLを返すだけのサーバーを狭義的にWebサーバーと呼び,PHPやPythonで書かれたプログラムが動き,それをHTTPとバインドできるサーバーをアプリケーションサーバーと呼ぶようです.今までも「NGINX+gunicorn+Pythonを動かす.」ようなことはできました.しかし,厳密にはgunicornのようなWSGIサーバー(アプリケーションサーバー)とNGINXが連動していただけです.この例で言うと,gunicornの部分をNGINX Unitが代替するようなイメージです.
 RESTful APIによるサーバーの設定という特徴は,他のサーバーでは聞いたことがないです.一般的には/etc/confとかに設定ファイルを置いて,再起動するイメージですね.

環境の準備

先人の方々の知恵をお借りしながら開発環境を進めました.

PHP, Python, Golang を NGINX Unit で動かしてみた

ほとんど,公式のドキュメントどおりです.今回も環境はUbuntuでpre-compiledなバイナリを使ってしまいます.

$ wget http://nginx.org/keys/nginx_signing.key
$ sudo apt-key add nginx_signing.key

以下の文章を/etc/apt/sources.listに追記

deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

そして,インストール

$ sudo apt-get update
$ sudo apt-get install unit unit-dev

サーバーの起動

$ sudo service unitd start

調査には,若干時間がかかりましたが,割と簡単に入る方だと思います.

ちなみに上記で入るNGINX Unitのphpとpythonのバージョンも見てみました.

$ pwd
/usr/lib/unit/modules
$ ls
php.unit.so  python.unit.so

/usr/lib/unit/modulesの中にphpとpythonのsoが入っているようです.これを

$ objdump -s php.unit.so  | less
$ objdump -s python.unit.so  | less

と目視でバイナリダンプを読んでいきました.その結果,

  • php 7.0.22
  • python 2.7.12

こんな感じのバージョンでした.
恐らくUbuntu 16.04の公式のpythonはまだ2.7系だからみたいですね.python3系でプログラムを書きたい方はソースコードからビルドするしかないのかな?

基本的なサーバー設定

今回,ベンチマークで使ったコードや設定ファイルはリポジトリに用意しました.

https://github.com/kotauchisunsun/nginx_unit_bench

サーバーの設定はすごく簡単で,

$ sudo curl -X PUT -d @php_config.json  \
       --unix-socket /run/control.unit.sock http://localhost/

このようにjsonを投げ込むだけで,サーバーのリスタート要らずで設定可能です.
Ubuntuで行う注意点としてはsudoで投げることと,ソケットが/run/control.unit.sockで動いてることですね.
あとリポジトリにあるjsonはそのままでは動きません.動かすソースコードのパスや実行ファイルのパスが絶対パスで書かれているので,適宜書き換えてご利用ください.

ベンチマーク

ベンチマークする言語はPHP,Python,Goです.
アプリケーション自体はすごく簡単で,

  • PHPは「Hello PHP World!!」
  • Pythonは「Hello Python World!!」
  • Goは「Hello Go World!!」

と出力するだけのアプリケーションです.
出来るだけ簡単にすることで,NGINX Unitとプログラミング言語とのブリッジで一番速いものは何か?を調べます.

ベンチマークに使用するソフトはApache Bench

でも使った簡単に使える負荷テスト用のツールです.
コマンドはこんな感じ

$ ab -n 100000 -c 100 http://localhost:8100/ 

100並列で10万リクエストを送り込みます.(リポジトリ内のbench.shがベンチマーク用の実行スクリプトです.)

ベンチマーク結果

Language min Response Time[ms] mean Respones Time[ms] max Response Time[ms] Request Per Second
PHP 6 99 1,414 1005.30
Python 65 141 1,916 708.99
Go 48 106 2,567 946.90

image.png

image.png

詳しいデータはリポジトリに,php.result, python.result, go.resultとして置いています.

考察

大方の予想を裏切る結果かもしれませんが,NGINX Unitで動く最速の言語はPHPでした.
理由ですが,なんとなくは分かります.それはコードを見ると,

index.php
<?php
    echo "Hello PHP World!!";
wsgi.py
from flask import Flask

application = Flask(__name__)


@application.route("/")
def index():
    return "Hello Python World!!"
hello.go
package main

import (
    "fmt"
    "net/http"
    "unit"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello Go World!!")
    })
    unit.ListenAndServe(":8300", nil)
}

となっています.pythonの場合はwsgiの形式でアプリケーションを作る必要があります.そのため,Flaskやwsgiのモジュールを読み込むのが必須になっています.また,Goも同様で,NGINX Unit用の"unit"というモジュールとバインドすることが必須になっています.その一方で,PHPはライブラリのロードが一切ありません.個人的には,「バイナリで動くGoが一番速いだろう」と思っていました.しかし,そのGoが遅い.そして,PHPのmin Response Timeが6[ms]という異様に速いことを考えて,総括するとモジュールを利用している分,Python,Goは遅く,PHPが最速であった.ということが分かります.

まとめ

  • Ubuntu上でNGINX Unitのインストールを行った.
  • 言語別のベンチマークをしてみた結果,PHP > Go > Pythonだった.
  • PHPが最速である理由はモジュールのロードが少ないからだと思われる

という結果でした.
やはり,衝撃的だったのは「PHPが最速だった」ということでしたが,正直PHPとGoはそれほど性能は変わらないかな?と思っています.最終的にはロジックを作りこんでいくと,Goの方が速くなるだろう.と思っています.一方で,ちょっとPythonは遅いかな?と感じざるを得ないです.
あと上のベンチマークでは記載しませんでしたが,Goのモジュールはレスポンスに失敗することがあります.これは原因はあまり追っていませんが,負荷テストを行ううちに

apr_socket_recv: Connection reset by peer (104)

と出力され,エラーで落ちました.そのため,計3回ぐらいベンチマークを取り直しました.まだまだNGINX Unitもβ版のようなので,こういうこともあると思います.
 一方で,秀逸だなーと思うのはREST FullAPIでサーバーの設定ができることです.これは素晴らしい.ベンチも行いやすかった.しかし,もうちょっとjsonのつくりをこだわって欲しかった.リポジトリ内のconfig用のjsonを見ていただければわかりますが,言語別にpathの表記が微妙に異なったりしています.そこにはまっているが,気づかずcurlでAPIを叩くわけなんですが,エラーメッセージが不親切です.エラーメッセージでjsonのどの部分で間違っているのか指示してくれないので,自分で目を皿のようにしながら公式ドキュメントとにらめっこする必要があって,しんどかったです.
ともあれ,一度慣れてしまうとNGINX Unit楽かもしれないです.僕はPHPが好きではないです.その理由はphp.iniでopen_base_dir等を設定しないといけない(しかもその周りでよくハマる)ですが,その辺が丸っと無視出来て,どのパスのソースコードでも動かせるようになってます.その設定もjsonで一発で指定できるので非常に楽です.これはPythonの場合もそうで,gunicornやらnginxやらの良く分からない設定を頭抱えながら設定することを考えるとずいぶん楽な印象です.しかもサーバーをリスタートする必要がないので,開発が非常にやりやすく,Try & Errorが高速で出来ます.
本番投入はまだまだ難しいかもしれませんが,ハッカソンぐらいの軽いプロダクトであれば,言語ビルドインのサーバーとか使うよりかは,ルーティングや設定が取り回しやすいので,ありなんじゃないかなーと感じました.是非触ってみてください.

191contribution

Golang の fmt の部分は下記のような書き方にすると削れますよ

package main

import (
    "net/http"
    "unit"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello world"))
    })
    unit.ListenAndServe(":8300", nil)
}
691contribution

@Ress
コメントありがとうございます!
なるほどーそういう書き方もできるのですね.Goはあまり慣れていないので助かります!

191contribution

@kotauchisunsun
Go はネット上の情報量がそれなりにあるので調べながら慣れていくといいと思いますよ!

(できれば fmt を使用しない場合も計測して記事を更新して欲しいです