- CGIプログラミングでも1日2億件以上のWebリクエスト処理が可能
- 近年のハードウェア性能向上により、CGI方式の欠点は大幅に縮小
- GoとSQLiteを活用したCGIプログラムが16スレッドCPUで卓越した性能を示す
- CGIは複数CPUコアの活用に特に適した構造を提供
- 現代技術により、過去のWebアプリケーション開発方式も十分に実用的である可能性を示す
CGIの過去と現在
- 1990年代後半、筆者はCGIでWeb開発を始め、当時はNewsProのようなシステムを使っていた
- CGIはWebリクエストごとに新しいプロセスの起動と終了を繰り返すため、高いオーバーヘッドが発生した
- そのため、より効率的なPHP、FastCGIなどの代替技術が開発された
ハードウェア性能の進化
- この20数年の間に、コンピュータの速度と性能は急激に向上した
- 2020年に筆者はGoやRustで開発されたツール(ripgrepなど)を活用し、プロセス実行方式の実用性を再発見した
現代的なCGI方式の利点
- GoやRustのように実行速度の速い言語でCGIを実装すれば、旧来のCGIの欠点の大半は解消される
- CGIプログラムはリクエストごとに別プロセスとして動作することで、マルチコアCPUの活用に最適化される
- 例えば、16スレッド環境では毎秒2400件以上のリクエスト、つまり1日2億件超の処理が可能であることが確認された
- 大規模サーバーでは384以上のCPUスレッドを提供できる
開発文化への示唆
- 現在はGoやRustのような言語の導入により、1990年代のCGI方式が再び意味を持ち得る
- ただし、依然としてあらゆる環境に適した方式ではなく、主流の方式として推奨されるわけではない
- 重要なのは、現時点ではCGIが以前ほど非効率なソリューションではないことを実験的に示した点である
結論
- 現代のハードウェアと高速な言語サポートにより、CGIプログラミングは過去とは比較にならない性能を示す
- マルチプロセスの利点を最大限に活用できる事例として、Web開発者に興味深い示唆を与える
1件のコメント
Hacker Newsの意見
最近では、たとえPythonでもCGIはかなり高速だという実感がある
もしCGIスクリプトが起動時にCPUを400ミリ秒使うとしても、サーバーに64コアあれば毎秒160リクエストを処理でき、1日あたりサーバー1台で1,400万件のトラフィックをさばける
1日あたり億単位のトラフィック(静的アセット除く)でも、CGIプロセスの起動がボトルネックになるわけではないということ
昔はこういう技術は「退屈なくらい安定した技術」だからPython標準ライブラリにずっと入っていたのだと思っていたが、最近のPythonメンテナーはむしろ安定性や後方互換性に否定的な立場だ
そのため、あまりに「退屈で安定した」モジュールは標準ライブラリから削除されつつあり、実際に
cgiモジュールは3.13で削除された25年近くPythonをプロトタイピングに使ってきた習慣のせいだが、今では後悔している
JSとLuaのどちらにするかで揺れている
cgi削除に関する公式説明リンクは PEP 594 cgiこのリンクから2000年(25年前)に書かれた PEP 206 にたどれるが、すでにその時点で「
cgiパッケージは設計がよくなく、手を入れにくい」と説明されているjackrosenthal/legacy-cgi リポジトリで、標準ライブラリモジュールをそのまま置き換えられるdrop-in replacementを確認できる
Python開発者が外したのは
cgiという名前のモジュールだけだCGIスクリプトの実装自体は、今でも
http.serverモジュールのCGIHTTPRequestHandlerでサポートされているもともと
cgiモジュールには、HTMLフォームデータをパースする関数がいくつか入っていただけだという指摘Pythonで
cgiモジュールが標準ライブラリから外れることを批判するのは理解できるが、よく比較対象に挙がるJSにはそもそも標準ライブラリがないLuaにもstdlibにCGIモジュールはないという指摘
個人的にはPHPかJSのほうが好み
この手の用途では、箱から出した時点でJITが使えるので便利だ
Pythonは1.6の頃から使っているが、主にOSスクリプティングにしか使ってこなかった
昔はTclをApacheやIISのモジュールとして連携しつつ、結局またCでモジュールを書き直すことを繰り返していた経験がある(1999〜2003年)
CGIスクリプトが400ミリ秒のCPUを使うなら、そのエンドポイントの応答速度も最低その程度になってしまい、使い勝手に響く
最近、350ドルのミニサーバーにgolangバイナリ、rabbitmq、redis、MySQLを載せて、同じサーバー上で5,000 req/sを継続処理している
24時間なら4億リクエスト処理できる性能だ
最近の無料ツールは本当に素晴らしいと実感する
それでもクラウドのコストは高すぎると思う
もちろん1対1で比較はできないが、開発とチューニングを自宅の地下サーバーで直接できた満足感は大きい
Kubernetesベースのマイクロサービスの塊を使って、開発速度が10倍遅くなる場合もある
サーバーというのは毎秒1リクエストしか処理できない機械ではない、という事実を知らないケースが多い
Googleがやっているからという理由だけで、過剰なオーバーヘッドを払っている現実がある
自分も、うちのチームに合っている「モジュラー・モノリス」アーキテクチャについて記事を書こうと思っている
サイドプロジェクトを自宅でセルフホストしようとしたが、停電、ISPのダウンタイム、リモートアクセス不可、ハードドライブ故障などリスクが大きい
結局、自分の時間まで考えると経済的メリットは微妙だった
クラウドサービスは規模の経済を享受できるので、実際には合理的な選択だ
わざわざクラウドでなくても、ホスティングプロバイダから専用サーバーを借りることもできる
もちろん帯域幅やトラフィック制限はある
クラウドが主流なのは、VCや投資家がその企業の株式を持っているか、「無限にトラフィックが爆発するかもしれない」という不安のせいだ
クラウド営業の専門家は、投資家の不安につけ込むのがうまい
必ずしもクラウドだけを使うわけではない
実サービスでVMコストが高い理由は、高性能コンピュートが必要だからではなく、莫大な容量のローカルディスクが必要だからだ
高い計算能力は不要で、20TBのハードディスク4台と適度なCPUがあれば、かなりのサービスを構想できる
クラウドではこうした組み合わせを見つけるのはほぼ不可能だ
cgi-binでDBアクセスが必要な場合、毎回プロセスがDBコネクションを新規作成するのが不便コードがメモリ内で動く方式(fastcgiなど)なら、単に起動時間を減らせるだけでなく、DBコネクションプールやスレッドごとの永続コネクションを維持できる
大規模で回すとDBコネクション数が多くなりすぎてDBが苦しくなる現象がある
「Pythonはシングルスレッドだから複数プロセス、Pythonは遅いからさらに多くのプロセス」という理由で多数のプロセスを動かす
結局はPythonプロセスの外側にshared connection pool(pg bouncerなど)を分離し、さまざまなチューニングが必要になる
最後には、扱いやすい言語(マルチスレッド対応で性能のよい言語)で再実装したら、ずっと単純になった経験がある
だから結局、CGIはリクエスト間で情報を保持するモデル(fastcgiなど)へ発展した
伝統的には独立したデーモンを立ててプロキシ役にすることもあり、接続もUnixソケットを使えばTCP/IPよりずっと効率的だ
UDPを使えという意見
自分にとってinetdこそがまさにCGIそのものだ
そのおかげでインターネットがずっと面白くなったという印象がある
実際にinetdでいろいろなシェルスクリプト、さらにはBashだけで書いたHTTPまで動かしていた時代があった
古いVPSや、バックアップもバージョン管理もしていなかったノートPCなどは消えてしまったが、楽しい思い出だ
デプロイもMakefile + scpで単純だったし、テストもnetcatとgrepで書いたBashスクリプトでできた
本当にいい時代だと感じる
hello worldアプリで2400 rps(毎秒リクエスト処理数)を達成したというのは、今のハードウェア基準ではあまり大したことがない印象
コードが簡単になったわけでもないのに、いったい何のために性能を犠牲にしているのか疑問だ
2000 rps超を処理する必要がないなら問題はない
そういうトラフィックが必要なサイトはごく少数だ、という主張
数字だけ見れば高くはないが、実際には多くの環境で十分だ
HN開発者がよく話す「hug of death」(特定の瞬間に流入が急増する状況)にも耐えられる水準だ
うちの会社では今でも
cgi-binディレクトリで、簡単な社内向けWebアプリを素早く立ち上げる方式を使っているシンプルに使う分には開発効率が非常に高い
CGIでも
http/1.0を直接printしなくてよく、Pythonのwsgiref.handlers.CGIHandlerを使えばどんなWSGIアプリでもCGIスクリプトとして実行できるFlaskのサンプルコードも次のようにシンプルだ
実運用ではuwsgiのCGIプラグインでスクリプトを実行している
Apacheやlighttpdで
mod_cgiを動かすより、ずっと簡単で柔軟だという印象uwsgiはシステム単位で動くので、systemdのハードニングやサンドボックス化をすべて使えるのも利点
さらに、uwsgiのCGIハンドリングではファイルタイプごとにインタプリタを指定できる
最初のバイト送信まで250〜350msで、うちの用途には十分許容範囲だ
uwsgiのCGI関連ドキュメント
wsgiref.handlers.CGIHandlerがまだdeprecatedになっていない点は有用な情報だ昨日議論されていた関連スレッド リンク
最近Apacheをサイドプロジェクトで使ったとき、
.htaccess機能が便利だったので使った経験があるどのディレクトリでも
.htaccessファイルを置くだけで、個々のリクエストごとに追加のサーバー設定を読み込めるhtaccess公式ドキュメント
昔はリクエストごとにディスクアクセスが発生するオーバーヘッドのため、性能面で
.htaccessは避けたほうがよく、なるべくメイン設定に統合すべきだとされていたしかし今ではSSDもRAMも十分あるので、もちろん性能はごくわずかに落ちるが、CPUが十分速いため大半のケースでは無視できるレベルだ
[自分のプロジェクトStaticPatch][https://github.com/StaticPatch/StaticPatch/…
PHPの生みの親Rasmus Lerdorfの名言
「私は本物のプログラマじゃなくて、ただ動くようにして先へ進むだけだ。本物のプログラマなら『メモリリークがひどいから直さなきゃ』と言う。私は10回ごとにApacheを再起動するだけだ」
PHPはその後長い道のりを経て、初期の失敗を乗り越えながら大きく発展した
「PHP 8では、私の書くコードは少ないほど良い」という言葉も残したという逸話
Apacheがファイルシステムを監視して、変更があったときだけ読み直すようにすればいいのに、なぜ毎リクエストごとに不要なディスクアクセスを発生させるのか理解できない
その結果、99.99%のHTTPリクエストが遅くなるという指摘
最近のワークフローでは、高速なプロトタイピングのためにこういう構造を検討している
JIT言語はfastcgi形式でなければimportがボトルネックになる
自分で使っていたh2o Webサーバーは、mrubyやfast-cgiハンドラの設定ファイルがシンプルで、ローカルスクリプト作業にちょうどよかった
h2o fastcgiドキュメント
もう一つの利点は、顧客が独自コードを追加できるようにローカルソフトウェアを拡張する場面でも使えることだ
たとえば従来は拡張のためにMCPを使わなければならなかったが、今ではCGIで構造化されたリクエストさえ実装すれば済む
エンドユーザー環境向けとして、MCPフロントにCGIプログラムをつなぐのも考えてみる価値のあるアイデアだ
MCPサービス自体も十分CGIで実装できる可能性がある
仕様をもう少し見てみる必要がありそうだ
fastcgiでは、CGIの持っていた利点のほとんどが失われるのではないかという疑問
昔、CプログラムとCGIの組み合わせを直接使っていた
当時は100個を超えるコアも十分なRAMもなく、最大でも1GB程度のメモリで動かしていた
当時できていたことを考えれば、今ははるかに簡単なはずだという確信がある
負荷分散が必要になってからはスケールさせにくかったが、それまではかなりうまく動いていた事例だ
ちなみにフロントとバックオフィスで別々の実行ファイル2本だった