7 ポイント 投稿者 GN⁺ 2025-07-07 | 4件のコメント | WhatsAppで共有
  • 初期のWeb時代に広く使われていた CGIプログラム が、現代のハードウェアでも依然として高い性能を発揮できることを実験で確認
  • CGIはプロセスごとにリクエストを処理するため、メモリ管理が自動で行われ、デプロイがシンプルという利点がある
  • ベンチマーク結果として、一般的な16スレッドCPUサーバーでも毎秒2400件以上、1日2億件以上の リクエスト処理が可能 であることを実証
  • Go言語とSQLiteで書かれた guestbook.cgi のサンプルコードとDockerfileをオープンソースで公開
  • CGIは現在では一般的ではないものの、依然として実用的でモダンな代替手段 になり得ることを示している

CGIプログラムと動作原理

  • 2000年代初頭には、CGI(Common Gateway Interface)プログラム が動的Webサイト構築の主要な方式だった
  • 多くは PerlC言語 で書かれており、性能向上のために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プログラムを ApacheGo net/http サーバー の両方でテスト
  • guestbook.cgiプログラムの説明

    • 訪問者がWebサイトの下部にコメントを残せるシンプルなゲストブックプログラムを実装
    • Go言語とSQLiteを使用し、できるだけシンプルでありながら現実味のある設計
    • ソースコードとDockerfileをGitHubで公開
  • 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件のコメント

 
kansm 2025-07-09

えっ… CGIをまた使うことになるんですか??(笑)
わあ… いつの時代のCGIだろう…

 
tujuc 2025-07-08

7/7付けで更新された内容があるようですね。

Serving a half billion requests per day with Rust + CGI

5億リクエストとは…。

 
GN⁺ 2025-07-07
Hacker Newsの意見
  • 1990年代にも、Cで書かれたCGIプログラムが本当に高速だった環境を覚えている。ただし、エラーが多発しがちだった点は欠点として認める。記事で言及されているGoのプログラムやNimのような現代的な言語も、データベース接続をしない限り、localhost上では非常に高速でレイテンシが小さい印象がある。CLIユーティリティで fork & exec を使っているような感覚で、ネットワークレイテンシと比べればコストはほぼ無視できる程度だった

    • ただし、特定の技術に依存しやすい文化にも言及。たとえばPythonインタプリタのように起動コストの大きい言語に慣れてしまうと、マルチショットあるいは永続的なモデルを必要とするようになる

    • 初期のHTTPのワンショットモデルは、FTPサーバーが何百ものアイドルなログインセッションを長時間維持できるほどのメモリを持っていなかった問題から出発していた

    • CGIで pre-forking(レイテンシを隠せる)とRustのような安全な言語を組み合わせれば、優れたシステム設計が可能だという指摘。TLS終端処理はマルチスレッドのWebサーバー(またはCloudFrontのようなレイヤー)で処理できるため便利さが強調されている

      • 状態が残らず、コアダンプやデバッグが非常に容易な環境であり、主に線形的なリクエストモデルでスケールも簡単にできる
      • stdin から読み stdout に書くだけで済む簡潔さを称賛。WebSockets が多少複雑さを増すが、心配するほどではない
      • Javaの台頭により、fork() のコストとCの危険性を避ける目的でアプリケーションサーバーへの移行が急速に進んだ流れを想起。いまなら再び単純さへ戻れると主張
      • Rustは好きではないが、このような形のWebバックエンドコードが容易に書ける時代が来れば、node/js、php、python の開発者にも魅力的に映るだろうと期待している
  • CGI時代から開発を始めたことで、短命なサブプロセスを回すことに強い反感を持つようになった経験

    • PHPやFastCGIは、Webリクエストごとに新しいプロセスを作る性能問題を避けるために作られたという背景説明

    • 最近のハードウェアの進歩のおかげで、プロセス起動コストは実際には大きな問題ではないという現実を知った

    • このベンチマークは毎秒2000リクエストを処理でき、数百程度しか処理しなくても複数インスタンスへ容易にスケールできる現代的な環境に言及

    • AWS LambdaをCGIモデルの再来と表現した意見に同意し、かなり的確な比喩だと考えている

    • もしCGIスクリプトを静的リンクされたCバイナリとして、サイズにまで気を配って配布していたなら、それほど失望はしなかっただろうと述べている

      • PHPインタプリタや各種ライブラリの読み込み、ファイルパースなど、動的リンクのプロセス起動コストのほうがはるかに大きい
      • Goを使うというのは、25年前でも十分に競争力があり得たやり方だったと確信している
      • SQLiteデータベースを開くのは、コンテキストスイッチでソケットを渡すのとほぼ同等の性能で、リモートの mysql 接続と比べればはるかに高速である点を強調
      • FastCGIは新しいアプリケーションに対しても、いまなお優れた選択肢だと主張
    • CGIは低負荷環境では、金銭面でも性能面でも大きな負担ではなかった

      • 高負荷時には、FastCGIのような常駐プロセスのほうが有利
      • CGIでも毎秒2,000 rps まで処理可能だが、FastCGIならはるかに高い性能を達成できる
      • 別個のサーバープロセスを追加し、アップグレード時には再起動するだけでよく、性能が重要なときにはその価値があると述べている
    • Goが登場する前は、CGIプログラムをC/C++で作るのは安全性・開発難易度の両面でハードルが高かった2000年代の状況

      • PerlやPythonはインタプリタ起動やコンパイルのコストがかなり大きく、Javaは実質的にさらに遅かった
      • AWS Lambda = CGIモデルの生まれ変わりに近いという点に同意
      • いまはマネージドFastCGIとほぼ同じモデルに戻ってきた感じがする
      • 単に実行ファイルをアップロードして動かせば済むはずなのに、複雑さばかり追加する技術の洪水が残念だ
  • 今日のサーバーには384 CPUスレッドが載っており、小さなVMでさえ16 CPUを持てる時代

    • こうしたハードウェアでKestrelを使って開発すれば、1日に何兆回ものリクエストでも難なく処理できる

    • PHPに似た開発体験を string interpolation 演算子で提供できる

    • LINQと String.Join() を活用すれば、HTMLテーブルやネストした要素も簡単にテンプレート化できる

    • 本当に難しいのは、MVC/Blazor/EF のようなエコシステムの地雷原をうまく避ける方法を知ること

    • プログラム全体を1つのトップレベルファイルとしてCLIから実行する方式も可能だが、"Minimal APIs" というキーワードを知らないと、誤ったドキュメントの迷路に入り込みやすい

      • コア技術の上に抽象化レイヤーを積み重ねることで、Director/VP の役職へ昇進していく事例があまりにも多いことに驚く
  • CGIの利点は、マルチテナント環境で隔離のプリミティブ機能を新たに構築する必要がない点

    • 1つのリクエストにバグがあっても、プロセス分離のおかげで他のリクエストには影響しない
    • 無限ループも、プリエンプティブスケジューリングのおかげでサービス拒否(DoS)にはつながらない
    • rlimit で長時間かかるリクエストを強制終了できる
    • cgroup を使ってテナントごとのメモリ、CPU、ディスク/ネットワークIO使用量を公平に割り当てられる
    • namespace/ jail、権限分離によってリクエストごとのアクセス権限を制限できる
  • CGIスクリプトのおかげで perl が高速な起動時間のために最適化されていた理由

    • time perl -e '' コマンドを実行すると、perl は 5ms、python3 は 33ms、ruby は 77ms で、perl の高速な起動時間が確認できる

      • tcc mob branch の #!/bin/tcc -run 方式のスクリプトは perl より1.3倍速いと述べている
      • Julia、Java VM、thread PHP なども起動時間が非常に長くなる例
      • 人々が「大きな環境」に習慣的に依存するようになる現象
      • Lispコミュニティでもイメージを使うことでこれが繰り返され、「emacs is bloated」というミームもここから生まれた
      • 90年代中盤から後半にかけてのPerl全盛期は、本当にCGIのおかげで成り立っていた空気があった
      • 当時は getline でさえ標準ではなく、サードパーティのCライブラリを数百〜数千行で作ったりしていた時代を回想
      • 結局は「評判」によって技術が選ばれ、学習の大半は友人の勧めを通じて行われる
  • 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 を制作

    • デスクトップブラウザで最高の体験を提供
    • HNトラフィックデータをもとに、実際の動作フローをWeb上で確認できる
  • 最近の複雑なアーキテクチャ志向には疑問を持っていたが、実際には良いハードウェアがあれば既存技術で十分かもしれないという指摘

    • 数百万人にリアルタイムの株価情報を知らせるシステム設計を問われたとき、最初はKafka、pubsub など複雑なストリーム構造を思い浮かべたが、最終的にはサーバーに静的ファイルを置く単純な方法も検討した

    • こうした方式の実運用コストが気になる

      • 実質的に、あらゆるWeb APIのレイテンシはDBクエリやMLモデルへのクエリで決まる
      • それ以外のプロセスはPythonのような遅い言語を使っても大した問題ではない
      • 変化の少ないデータだけを返すなら、NICの限界まで簡単に到達できる
  • serverlessアーキテクチャに似ているが、はるかにシンプルで安価だと強調

    • 実際にビジネス現場でこのように使っている事例があるのか気になる
  • こうした伝統的な構造を見直さず、単に「serverless functions」という新たなパラダイムだけを作り出したことが残念

    • Lambdaのような serverless function には別個の保護メカニズム(マイクロVMなど)があるとはいえ、実際にはCGIと権限調整だけでも、はるかに少ない複雑さでかなり先まで行けたはずだと考えている
 
regentag 2025-07-07

cgiはさておき、jspに対する反応が意外ですね(笑)
jspはもうそのくらいの古代の遺物になってしまったのでしょうか。