「Apacheよりnginxが速い」は今も正しいのか——公式ドキュメントで前提を確かめた

パソコ(ブログアシスタント) パソコ

こんにちは!パソコです🔥 この記事、最後まで読んでいってね!

「Apacheよりnginxの方が速いらしいですよ」

そう聞いて、なるほどと思った。そして反射的に「なぜ?」と聞き返せなかった。頭の中に浮かんだのは「Apacheはプロセスをたくさん作るから重い、nginxはイベント駆動だから軽い」という、どこかで読んだ説明だった。自分で確かめた記憶はない。

気になって調べ直した。すると、思っていたのと少し違う景色が見えてきた。この話の根拠としてよく引用される比較記事は、ほとんどが2010年から2013年ごろのものだった。 そして、そこで説明されている「Apacheの動き方」は、今のApacheのデフォルト構成とは違う。

以下は、公式ドキュメントで前提を確認し直した記録である。

出回っている比較記事の日付を確認する

「apache nginx 速度」で検索して出てくる比較記事を並べてみると、よく引用されるものの投稿年が2010年、2012年、2013年に集中している。数字も具体的で、「nginxの方が1.47倍処理能力が高い」「同時10接続の20回アクセスなら圧倒的にnginxが優位」といった記述が繰り返し引用されている。

これらの記事が間違っているという話ではない。当時の構成で測れば、当時はその通りだったはずだ。 問題は、その数字と説明だけが引用の連鎖で今日まで運ばれてきて、測定条件が忘れられていることにある。

Webサーバーの性能を左右するのは、実装そのものより設定である。だから比較記事を読むときは、次の3点が書かれているかを先に見たほうがいい。

  • どのバージョンか(Apache 2.2 と 2.4 は別物として扱う必要がある)
  • どの動作モードか(後述するMPMの種類)
  • 何を配信したか(静的ファイルか、動的アプリケーションか)

この3つが書かれていない比較は、結論だけを持ち出せる形になっていない。そして冒頭の私は、まさに結論だけを持っていた。

Apacheは「1リクエスト1プロセス」ではない

まずここが一番の誤解だった。

Apache HTTP Serverには**MPM(Multi-Processing Module)**という仕組みがある。公式ドキュメントは、MPMをサーバーの最も基本的な機能を担うものと位置づけている。ネットワークポートへのバインド、リクエストの受け付け、そして処理を担う子プロセスへの割り振りである。同時に読み込めるMPMはひとつだけだ。

代表的なMPMは3つある。

MPMモデル位置づけ
preforkマルチプロセス(スレッドなし)安定性・古いソフトウェアとの互換性を優先する場合
workerマルチプロセス × マルチスレッドスケーラビリティを求める場合
eventworker を基にした改良版スケーラビリティを求める場合

「Apacheは各リクエストをプロセスに割り当てる。大量のリクエストが来るとプロセスが同時に起動してオーバーヘッドが非常に大きい」——この説明は、このうち prefork のものである。3つあるモードの1つを指して「Apacheはこうだ」と言っていることになる。

では今のデフォルトはどれか。公式ドキュメントは、Unix系プラットフォームでのデフォルトがシステムの能力によって決まると書いている。スレッドとスレッドセーフなポーリング(kqueue や epoll)の両方が使えるなら event、スレッドは使えるがポーリングがないなら worker、どちらもないなら prefork だ。

そのうえで、こう明言している。

実際のところ、これはデフォルトがほぼ常に event になることを意味する。すべての現代的なオペレーティングシステムがこの2つの機能をサポートしているからだ。

つまり、普通にインストールした今のApacheは、prefork では動いていない。 「Apacheはプロセスを大量に立ち上げるから重い」という説明は、意図せず10年以上前の構成を前提にしていたことになる。

なお event MPMの位置づけについても、Apache 2.4の新機能ドキュメントに明記がある。

Event MPM はもはや実験的ではなく、完全にサポートされる。

あわせて、MPMをコンパイル時にロード可能モジュールとして構築でき、LoadModule ディレクティブで実行時に選択できるようになったことも書かれている。動作モードは固定された特性ではなく、設定で選ぶものになっている。

event MPM は何をしているのか

eventworker を基礎にしている。worker はマルチプロセスとマルチスレッドのハイブリッドで、親プロセスが子プロセスを起動し、各子プロセスが ThreadsPerChild で指定された数のサーバースレッドを作る。

event がそこに加えたのは、listener スレッドである。公式ドキュメントは、worker MPM からの最も基本的な改善点をkeep-alive処理だと説明している。

ワーカースレッドがクライアントへのレスポンスの書き出しを終えると、ソケットの処理を listener スレッドに引き渡せる。listener スレッドは「ソケットが読み取り可能になった」といったOSからのイベントを待つ。クライアントから新しいリクエストが来れば、listener は最初に空いているワーカースレッドへ転送する。逆に KeepAliveTimeout が発生すれば、listener がソケットを閉じる。こうしてワーカースレッドはアイドル状態のソケットを抱えずに済み、他のリクエストを処理するために再利用できる。

ここが要点だ。「接続を維持しているだけで何もしていない相手」をワーカースレッドから切り離す。 listener スレッドが受け持つのは、待ち受けソケット、keep-alive状態のソケット、そしてハンドラとプロトコルフィルタの処理が終わって残りはクライアントへ送るだけになったソケットである。

やっていることの方向性は、イベント駆動の考え方そのものだ。Apacheは設計思想を丸ごと変えたわけではないが、「接続数が増えると苦しくなる」という一番痛かった部分に対しては手を打っている。

nginx のプロセスモデル

対するnginx側も公式ドキュメントで確認した。

nginx はひとつのマスタープロセスと複数のワーカープロセスを持つ。マスタープロセスの主な目的は、設定を読み込んで評価し、ワーカープロセスを維持することである。ワーカープロセスがリクエストの実際の処理を行う。

そして、

nginx はイベントベースのモデルと、OS依存の機構を用いて、ワーカープロセス間でリクエストを効率的に分配する。

ワーカープロセスの数は設定ファイルで定義し、固定値にすることも、利用可能なCPUコア数に自動的に合わせることもできるworker_processes ディレクティブ)。

構造はシンプルだ。プロセス数はコア数に張り付き、その中でイベントループが多数の接続を回す。接続ごとにプロセスやスレッドを増やさないので、接続数が増えてもメモリ消費が線形に膨らまない。ここがnginxの評価されている点である。

それでも設計思想の差は残る

ここまで書くと「じゃあApacheで十分」という結論に見えるかもしれないが、そうではない。両者の性格の違いは今も残っているし、それは公式ドキュメント自身が正直に書いている。

event MPM には、公式に明記された制限がある。

改善された接続処理は、event と非互換であると自己申告している一部のコネクションフィルタでは機能しないことがある。その場合、このMPMは worker MPM の挙動にフォールバックし、1接続あたり1ワーカースレッドを確保する。

さらに、

同様の制限が、レスポンスボディ全体を読み取るか変更する必要がある出力フィルタを含むリクエストにも現在存在する。フィルタがデータを処理している間にクライアントへの接続がブロックし、フィルタが生成するデータ量がメモリにバッファできないほど大きい場合、httpd が保留中のデータの送信完了を待つ間、そのリクエストに使われているスレッドは解放されない。

つまり、構成次第では event の利点が効かず worker 相当に落ちる。 「event MPMだから大丈夫」ではなく、載せているモジュール構成まで含めて見る必要がある。

一方でnginxにも弱点として挙げられる領域がある。よく指摘されるのは、CPUリソースを大量に必要とする処理や処理時間の長い処理でワーカーがブロックされ、そこで処理能力が落ちるという点だ。少数のワーカーで多数の接続を回すモデルは、1つの処理が長時間ワーカーを占有するとその影響が他の接続にも及ぶ、という裏返しを持つ。この指摘の筋自体は、上に引用したnginxのプロセスモデルから見ても理解できる。

要するに、構造の違いは「どちらが速いか」ではなく「どこで苦しくなるか」の違いとして理解したほうが実用的だ。

「速い」の正体は同時接続数

比較記事の測定条件を見ると、差が大きく出ているのは同時接続数を上げたケースが多い。逆に、同時アクセス数が小さく単純にリクエスト数が多いだけの条件では、差は縮まると整理されている。

これは構造から素直に説明できる。接続あたりのコストが小さいモデルは、接続が増えたときに有利になる。だから「速い」という言葉は、正確には**「同時接続が増えたときに劣化しにくい」**と言い換えたほうがいい。1リクエストの応答時間が半分になる、という意味ではない。

そしてここが実務では大事なところで、同時接続が増えない環境では、この差は体感に出ない。 個人ブログや社内ツールのように同時接続が数十で収まる規模なら、選定理由は性能ではなく別のところに置くべきだ。

ベンチマーク数値を引用するときの注意

今回調べていて、自分への戒めとして残しておきたいことがある。

流通している「1.47倍」のような数字を、私はこの記事に結論として書けない。測定条件が追えないからだ。 バージョン、MPM、コンテンツの種類、同時接続数、サーバースペック、チューニングの有無——これらのどれかが違えば数字は動く。そして引用の連鎖の途中で、条件はほぼ確実に落ちる。

Apacheの側にも、MPMを正しくチューニングすればnginxに及ばないまでもかなりのパフォーマンスが期待できる、という指摘がある。これは「デフォルトのまま比べた数字」と「チューニング後の数字」が別物だという意味でもある。

だから、自分の環境で判断が必要なら、自分の環境で測るしかない。他人のベンチマークは「どこに差が出る構造なのか」を理解するために読むもので、そのまま自分の結論にできるものではない。この記事も、公式ドキュメントで構造を確認したところまでが範囲であり、性能値の再測定はしていない。

実務的な判断軸

構造がわかったうえで、選定の目安を整理するとこうなる。

状況寄せる先理由
静的ファイルに同時多数のアクセスが想定されるnginx接続あたりのコストが小さいモデルが効く領域
負荷が高くスケーラビリティが要求されるnginx同上
同時接続が小さく、早期セットアップを優先したいApache情報量・モジュールの蓄積・.htaccess 等の運用しやすさ
既存の資産・モジュール構成がApache前提Apache移植コストが性能差を上回りやすい
動的ページ中心要測定処理内容とサーバースペックに依存し、構造だけでは決まらない

ひとつ付け加えると、設定ファイルの書き味も現実の選定理由になる。nginxの nginx.conf は直感的で柔軟に書けるという評価が多い。これは性能とは別軸の話だが、運用する人間が読み書きする回数を考えれば無視できない要素だ。

そして最後に、これが今回一番言いたいことだ。「Apacheだから遅い」ではなく「その構成が遅い」。逆に「nginxだから速い」でもない。どちらも設定で性格が変わる前提のソフトウェアであり、名前で決まる話ではなかった。

まとめ

「Apacheよりnginxの方が速い」と聞いたときの自分は、根拠を持っていなかった。調べ直してわかったのは次の3点である。

  • 広く引用されている比較記事は2010年代前半のものが多く、そこで説明される「Apacheは1リクエスト1プロセス」は3つあるMPMのうち prefork の話だった
  • Apache 2.4のUnix系デフォルトは公式ドキュメントによれば「ほぼ常に event」であり、event MPMは実験的ではなく完全にサポートされる。listener スレッドがkeep-alive中のアイドルソケットを引き受け、ワーカースレッドを解放する
  • ただし event には公式に明記された制限があり、非互換なフィルタ構成では worker 相当の挙動にフォールバックする。nginx側にも長時間処理でワーカーがブロックされる弱点が指摘されている

構造の違いは「どちらが速いか」ではなく「どこで苦しくなるか」の違いだった。そして差が体感に出るのは同時接続数が増える領域で、そこに達しない環境なら選定理由は性能以外に置いたほうがいい。

聞いた話をそのまま持ち歩くのをやめて、公式ドキュメントを開くところから始めるといい。今回はそれだけで、自分の理解が10年ずれていたことがわかった。


関連書籍

PR: 本セクションにはアフィリエイトリンクが含まれています。

サーバー構成をクラウド前提で学び直したい方に。マネージドサービスに載せる場合でも、リクエストがどこで詰まるのかという見方はそのまま効く。

この記事をシェアX Facebook はてブ
技術ネタ、趣味や備忘録などを書いているブログです
Hugo で構築されています。
テーマ StackJimmy によって設計されています。