- 初期のWeb時代に広く使われていた CGIプログラム が、現代のハードウェアでも依然として高い性能を発揮できることを実験で確認
- CGIはプロセスごとにリクエストを処理するため、メモリ管理が自動で行われ、デプロイがシンプルという利点がある
- ベンチマーク結果として、一般的な16スレッドCPUサーバーでも毎秒2400件以上、1日2億件以上の リクエスト処理が可能 であることを実証
- Go言語とSQLiteで書かれた guestbook.cgi のサンプルコードとDockerfileをオープンソースで公開
- CGIは現在では一般的ではないものの、依然として実用的でモダンな代替手段 になり得ることを示している
CGIプログラムと動作原理
- 2000年代初頭には、CGI(Common Gateway Interface)プログラム が動的Webサイト構築の主要な方式だった
- 多くは Perl や C言語 で書かれており、性能向上のためにCが選ばれることもあった
- CGIの概念はシンプルだが強力
- Webサーバーは環境変数にリクエストのメタデータ(HTTPヘッダー、クエリなど)を設定
- 別プロセスを生成してCGIプログラムを実行
- リクエスト本文をstdinで渡す
- プログラムのstdoutをHTTPレスポンスとしてキャプチャ
- stderr出力をサーバーのエラーログに送る
- プログラムがリクエスト処理を終えるとプロセスが終了し、ファイルディスクリプタやメモリが自動解放 される
- 開発者の立場では、新バージョンのデプロイも cgi-bin/ ディレクトリにファイルをコピーするだけで完了するため非常に簡単 だった
Hug of death(トラフィック急増)
- 2000年代初頭のWebサーバーの多くは、1〜2 CPU、1〜4GBメモリ環境が一般的だった
- Apache Webサーバーは接続ごとにhttpdプロセスをforkする構造のため、多数の接続時にはメモリ要求量が増加した
- 同時接続数は100を超えにくく、有名サイトにリンクされるだけでサーバーが簡単に過負荷 になることが多かった
- ( Slashdot Effect : 当時有名だったSlashdotにリンクが載るとトラフィックが殺到した。今でいうHacker Newsのトップに載るのに近い)
現代のサーバー環境におけるCGI
- 現在では 384個のCPUスレッド を持つサーバーも登場しており、比較的小さなVMでも16 CPUを利用できる
- CPUとメモリ性能は大幅に向上した
- CGIプログラムは別プロセスベースのため、マルチコアを自然に活用できる
- こうした点から、現代のハードウェアでCGIプログラムがどれほど高速かを直接ベンチマークした
- 実験は AMD 3700X(16スレッド) サーバーで実施
ベンチマークの主な結果
- シンプルなCGIプログラムを Apache と Go
net/http サーバー の両方でテスト
-
guestbook.cgiプログラムの説明
- HTTP負荷生成ツール
plow を使い、16接続で各10万件のリクエスト を実行
- 一般的なハードウェア上でも 毎秒2,400件以上、つまり 1日2億件以上のリクエスト処理 が可能
- 現在CGIは主流ではないが、依然として実運用のサービスでも利用可能
-
Apache環境でのwriteベンチマーク
- 毎秒 約2468件 のリクエストを処理し、平均 6.47ms のレスポンス遅延
- 10万件のPOSTリクエストを40.5秒で処理
- ほとんどのリクエストは7ms以内に応答し、100msを超えたのはごく少数のみ
- 実質的に高い書き込み処理性能を実証
-
Apache環境でのreadベンチマーク
- 毎秒 約1959件 のリクエストを処理し、平均 8.16ms のレスポンス遅延
- 10万件のGETリクエストを51秒で処理
- 半数以上のリクエストは8ms以内、最大遅延も31msにとどまった
- 読み取り性能も十分に優秀
-
Go net/http環境でのwriteベンチマーク
- 毎秒 約2742件 のリクエストを処理し、平均 5.83ms のレスポンス遅延
- 10万件のPOSTリクエストを36.4秒で処理
- スループットは平均2,742 RPS、平均遅延5.8msで、数値上はApacheより優れた性能
- 95%以上のリクエストが6ms以内に処理された
- Go環境でのCGIも十分な実戦性能を持つ
-
Go net/http環境でのreadベンチマーク
- 毎秒 約2469件 のリクエストを処理し、平均 6.47ms のレスポンス遅延
- 10万件のGETリクエストを40.4秒で処理
- ほとんどのリクエストを7ms以内で提供可能
- 読み取りスループットと応答速度の両方でApacheと同等かそれ以上
結論とリンク
- CGIプログラムは 最新ハードウェア 上で、超高速な同時実行性、シンプルなデプロイ、OSによる自動リソース解放 といった 利点 を持つ
- 現代的なフレームワークに比べると極めてシンプルだが、一定規模のサービスであれば今でも実運用に活用できる
- ゲストブックのサンプルとベンチマーク実験データは以下のGitHubで公開
https://github.com/Jacob2161/cgi-bin
4件のコメント
えっ… CGIをまた使うことになるんですか??(笑)
わあ… いつの時代のCGIだろう…
7/7付けで更新された内容があるようですね。
Serving a half billion requests per day with Rust + CGI
5億リクエストとは…。
Hacker Newsの意見
1990年代にも、Cで書かれたCGIプログラムが本当に高速だった環境を覚えている。ただし、エラーが多発しがちだった点は欠点として認める。記事で言及されているGoのプログラムやNimのような現代的な言語も、データベース接続をしない限り、localhost上では非常に高速でレイテンシが小さい印象がある。CLIユーティリティで fork & exec を使っているような感覚で、ネットワークレイテンシと比べればコストはほぼ無視できる程度だった
ただし、特定の技術に依存しやすい文化にも言及。たとえばPythonインタプリタのように起動コストの大きい言語に慣れてしまうと、マルチショットあるいは永続的なモデルを必要とするようになる
初期のHTTPのワンショットモデルは、FTPサーバーが何百ものアイドルなログインセッションを長時間維持できるほどのメモリを持っていなかった問題から出発していた
CGIで pre-forking(レイテンシを隠せる)とRustのような安全な言語を組み合わせれば、優れたシステム設計が可能だという指摘。TLS終端処理はマルチスレッドのWebサーバー(またはCloudFrontのようなレイヤー)で処理できるため便利さが強調されている
CGI時代から開発を始めたことで、短命なサブプロセスを回すことに強い反感を持つようになった経験
PHPやFastCGIは、Webリクエストごとに新しいプロセスを作る性能問題を避けるために作られたという背景説明
最近のハードウェアの進歩のおかげで、プロセス起動コストは実際には大きな問題ではないという現実を知った
このベンチマークは毎秒2000リクエストを処理でき、数百程度しか処理しなくても複数インスタンスへ容易にスケールできる現代的な環境に言及
AWS LambdaをCGIモデルの再来と表現した意見に同意し、かなり的確な比喩だと考えている
もしCGIスクリプトを静的リンクされたCバイナリとして、サイズにまで気を配って配布していたなら、それほど失望はしなかっただろうと述べている
CGIは低負荷環境では、金銭面でも性能面でも大きな負担ではなかった
Goが登場する前は、CGIプログラムをC/C++で作るのは安全性・開発難易度の両面でハードルが高かった2000年代の状況
今日のサーバーには384 CPUスレッドが載っており、小さなVMでさえ16 CPUを持てる時代
こうしたハードウェアでKestrelを使って開発すれば、1日に何兆回ものリクエストでも難なく処理できる
PHPに似た開発体験を string interpolation 演算子で提供できる
LINQと String.Join() を活用すれば、HTMLテーブルやネストした要素も簡単にテンプレート化できる
本当に難しいのは、MVC/Blazor/EF のようなエコシステムの地雷原をうまく避ける方法を知ること
プログラム全体を1つのトップレベルファイルとしてCLIから実行する方式も可能だが、"Minimal APIs" というキーワードを知らないと、誤ったドキュメントの迷路に入り込みやすい
CGIの利点は、マルチテナント環境で隔離のプリミティブ機能を新たに構築する必要がない点
rlimitで長時間かかるリクエストを強制終了できるcgroupを使ってテナントごとのメモリ、CPU、ディスク/ネットワークIO使用量を公平に割り当てられるCGIスクリプトのおかげで perl が高速な起動時間のために最適化されていた理由
time perl -e ''コマンドを実行すると、perl は 5ms、python3 は 33ms、ruby は 77ms で、perl の高速な起動時間が確認できる#!/bin/tcc -run方式のスクリプトは perl より1.3倍速いと述べているapache tomcat 11 を使ってみると、
.jspファイルやJava servletアプリケーション全体(.war)を ssh でアップロードするだけでそのまま動く1つの共有JVMで最大性能を確保
DBコネクションプールやキャッシュなどもアプリケーション間で共有できる
本当に印象的な体験
実際の利用パターン次第ではある
大規模サービスには素晴らしいが、50個の小規模アプリケーションがそれぞれ1日に数百件しか処理しないなら、TomcatのメモリオーバーヘッドはCGIスクリプトベースのApache/Nginxに比べて大きすぎると指摘
ファイルを単純にコピーしてデプロイできた時代が恋しいという感想
なぜデプロイ工程がここまで複雑になってしまったのかと残念がる
いまでも Jetty でバックエンドWebアプリを楽しく使っているという体験共有
Tomcat/Jakarta EE/JSP スタックは意外なほどかなり堅牢だという感想
PHPのようにHTMLとコードを混在させて書くこともできるし、純粋なJavaルートも可能
WebSockets対応、シングルプロセス・マルチスレッドモデルなのでリアルタイム通信にも強みがある
必要ならリクエストごとにデータ共有も可能で、JSPコードは基本的にリクエストスコープに制限される
デプロイは本当に簡単で、webapps ディレクトリに新しいファイルをアップロードするだけでTomcatが自動的に新しいアプリをロードし、既存アプリをアンロードする
欠点としては、クラスローダーリークにより garbage collection に失敗することがあり得る点で、これはシングルプロセスモデルの宿命
apache リクエストの可視化ツール ibrahimdiallo.com/reqvis を制作
最近の複雑なアーキテクチャ志向には疑問を持っていたが、実際には良いハードウェアがあれば既存技術で十分かもしれないという指摘
数百万人にリアルタイムの株価情報を知らせるシステム設計を問われたとき、最初はKafka、pubsub など複雑なストリーム構造を思い浮かべたが、最終的にはサーバーに静的ファイルを置く単純な方法も検討した
こうした方式の実運用コストが気になる
serverlessアーキテクチャに似ているが、はるかにシンプルで安価だと強調
こうした伝統的な構造を見直さず、単に「serverless functions」という新たなパラダイムだけを作り出したことが残念
cgiはさておき、jspに対する反応が意外ですね(笑)
jspはもうそのくらいの古代の遺物になってしまったのでしょうか。