某技術コミュニティ Slack で話していた内容のまとめ。
推測が多分に含まれているので気をつけて欲しい。
WSGI を使ったり使わなかったりする謎
自分は普段 Python で Web アプリケーションを開発するときは aiohttp Server を使っている。
from aiohttp import web
from app import app
web.run_app(app)
だいたいいつもこんな感じのファイルを作って、本番環境で動かすときも (Docker コンテナで) python main.py
と実行しているだけ。何も難しいことはなく、シンプルに Web アプリケーションを動かしている。
しかし以前 Flask で Web アプリケーションを開発していたとき、本番環境で動かすときは uWSGI なるサーバを使っていた。
あれは何か?
なぜあのときは uWSGI が必要で、今回は使う必要がなかったか?
いや別に、「WSGI が Web アプリケーションのためのインタフェースであり、uWSGI はその実装のひとつである」ことくらいは知っている。
むかし Web アプリケーションを Perl (Amon2) で書いていたときは Plack (PSGI サーバ) を使っていたし、Ruby (Sinatra) で書いていたときは Unicorn (Rack サーバ) を使っていた。自分が過去に触った言語にはだいたい同じような仕組みがある。しかし Node.js + Express にはなかった。1 Node.js と他の言語は一体何が違ったのか?
疑問点をまとめると、こうなる。
- Perl + Amon2 や Ruby + Sinatra では Plack, Unicorn を利用するのに、Node.js + Express では利用しないのはなぜか?
- 同じ Python の Web アプリケーションフレームワークでも、Flask では uWSGI を利用するのに aiohttp Server では利用しないのはなぜか?
アプリケーション自身が HTTP を喋るかどうか
いま理解している範囲で結論を書くと、「Web アプリケーションフレームワークによって HTTP を喋ったり喋らなかったりするから」。
具体的にはこう。
- Amon2 で実装したアプリケーションは HTTP を喋らない (PSGI に対応しているだけ)
- Sinatra で実装したアプリケーションは HTTP を喋らない (Rack に対応しているだけ)
- Express で実装したアプリケーションは HTTP を喋る 2
- Flask で実装したアプリケーションは HTTP を喋らない 3 (WSGI に対応しているだけ)
- aiohttp Server で実装したアプリケーションは HTTP を喋る4
HTTP を喋らないアプリケーションはそのままでは動かせないので、対応する Web サーバと組み合わせる必要がある。5
では何故、フレームワークによって HTTP を喋ったり喋らなかったりするのか。
それは「Web サーバと Web アプリケーションは分離した方がいい (Web アプリケーション自身が HTTP を喋れる必要はない)」からだと思われる。
Web サーバは大量の HTTP リクエストを高速に処理する必要があり、それを実現するためのサーバを各フレームワークが実装するのはコストが大きいし無駄が多い。WSGI のようなインタフェースを策定してしまえば、Web サーバと Web アプリケーションを分離してそれぞれ自由に組み合わせて使うことができるようになる。(そう考えると「どうせ HTTP を喋るんなら SSL 終端とか静的コンテンツ配信とかも含めて全部 Nginx でやれるようにしてくれよ」という発想になるが、その発想から出てきたのが Nginx Unit なのかと考えると納得がいく)
並列処理できるなら不要説
じゃあ何故 Node.js + Express や Python + aiohttp Server は WSGI のようなインタフェースを使わずに HTTP を直接喋っているのか。
このふたつには共通点があって、どちらもイベントループによるノンブロッキング IO をサポートしている。
Node.js はランタイムそのものがイベントループだし、フレームワークがどうとか関係なくノンブロッキング IO である。
Python はブロッキング IO な言語なので、愚直に HTTP を喋る実装をすると、例えばあるリクエストを処理している間に他のリクエストをすべてブロックしてしまってまったくパフォーマンスが出ないということが起こる。そこでスレッドやイベントループで並列処理することを考えるが、GIL があるという制約下で並列処理するならスレッドよりもイベントループでやる方がコスパがいい (?) のではないか、となる。aiohttp Server は asyncio (イベントループ) に依存した作りになっているので、このタイプのノンブロッキング IO にあたる。
アプリケーション自身が並列処理に対応できているとどうなるかというと、愚直に HTTP を喋る実装をしても普通に並列でリクエストを捌くことができる。あとは単純に複数プロセス起動してロードバランスしてやるだけで大量のリクエストも捌くことができるようになる。だから WSGI を使って Web サーバと Web アプリケーションを分離する必要がない。
まとめ
- Ruby や Python などの Web アプリケーションフレームワークでは、並列で HTTP リクエストを捌く仕組みを実装するのが大変なので WSGI のようなインタフェースを策定した上で他の仕組みに投げて役割を分離している (と思われる)
- もともとノンブロッキング IO な Node.js + Express や Python の asyncio をベースに実装されている aiohttp Server は、アプリケーション自身で並列処理ができるので WSGI のような仕組みを使わずに直接 HTTP を喋っても問題がない (と思われる)
-
プロセス監視のために forever や pm2 を使うことはあったが、あれは別物なので今回は無視 ↩
-
正確には Node.js 標準の http モジュールで Express アプリを serve しているわけだが、WSGI のような独特なインタフェースを定義しているわけではないので直接 HTTP を喋っていると言っても過言ではない (と思う) ↩
-
Flask は built-in サーバ機能 (
app.run()
) があるので HTTP を喋るように見えるが、あれも結局 WSGI サーバ実装だし、簡易的な実装なので production 用途には向かない ↩ -
aiohttp Server って「Web アプリケーションフレームワーク」というよりかは「asyncio で HTTP を扱うモジュール」なので、そりゃあ HTTP 喋らなきゃおかしいよねという話ではある ↩
-
自分が勘違いしていたのは、上記に挙げたフレームワークはすべて HTTP を喋ることができて、それとは別に WSGI のようなインタフェースにも対応しているものだと思いこんでいた ↩