2 ポイント 投稿者 GN⁺ 2025-07-07 | 1件のコメント | WhatsAppで共有
  • Jake Goldは16スレッドのAMD 3700XでGo + SQLiteのCGIプログラムを動かし、1990年代式のCGIが現代のハードウェアでどの程度通用するのかを確かめた
  • 結果は、ありふれたハードウェアでも毎秒2400件以上、1日あたり2億件以上のリクエスト処理が可能であることを示した
  • CGIはリクエストごとにプロセスを起動・実行・終了するため、過去にはオーバーヘッドが大きく、これを減らすためにPHPFastCGIのような方式が登場した
  • Simon Willisonは2020年にdatasette-ripgrepでRust製のripgrep CLIを呼び出して検索を処理し、Webリクエスト中のプロセス実行は避けるべきだという長年の思い込みを改めることになった
  • GoやRustのように起動が速い言語とマルチCPU環境では、CGI的な処理が以前より現実的な選択肢になりうるが、一般的に推奨される方法ではない

現代ハードウェアで見直すCGIの性能

  • Serving 200 million requests per day with a cgi-binは、Jake GoldがGo + SQLite CGIプログラムで1990年代式CGIの性能をテストした記事
  • テスト環境は16スレッド AMD 3700Xベースのシステム
  • 核心となる結果は、CGIでもありふれたハードウェアで毎秒2400件以上、1日2億件以上のリクエストを処理できるという点
  • CGIは、到着したリクエストごとに別プロセスを起動し、実行後に終了する方式
  • 初期のWebコミュニティはこのオーバーヘッドを避けるため、コードをメモリ常駐させて追加コストを減らすPHPFastCGIを生み出した

CGIは昔ほど悪くないかもしれない理由

  • 過去のCGIのボトルネックの1つは、Perl、Python、Javaのように非常に高速な起動を目標に設計されていない言語でWebスクリプトを書いていたこと
  • 今日ではGoRustを使えば、CGIスタイルのリクエスト処理ははるかに効率よく動作しうる
  • Simon Willisonは2020年にdatasette-ripgrepを作った際、Rustで書かれたripgrep CLIツールをシェルから呼び出して検索を実行し、良い結果を得た
  • CGIプログラムは別プロセスとして実行されるため、複数CPUを活用する構成とも相性がよい可能性がある
    • 現代のサーバーは384 CPUスレッドを持つことがある
    • 小規模なVMでも16 CPUを持てる
    • CPUとメモリの性能も過去より大幅に向上している
  • 1998年式のWebアプリケーション開発手法も、GoRustと組み合わせれば興味深い実験対象になるが、ほとんどのサービスが従うべき基本選択肢ではない

1件のコメント

 
GN⁺ 2025-07-07
Hacker Newsの意見
  • Pythonのようなものを使っても、最近のCGIはかなり速い
    CGIスクリプトの起動にCPU 400msかかり、サーバーが64コアなら、秒間160リクエスト、サーバー1台あたり1日1,400万ヒットを処理できる
    静的アセットを除いて1日数百万リクエストでも苦しいWebサービスなら、ボトルネックはCGIプロセスの起動時間ではない
    昔なら「Python標準ライブラリで長くサポートされてきた退屈な技術」と言っただろうが、残ったPythonメンテナーたちは安定性と後方互換性を有害だと見ているようで、退屈すぎて安定しすぎたモジュールを標準ライブラリから削除してきた
    cgiモジュールはPython 3.13で削除された
    この25年の大半を毎日使ってきたので、今でもプロトタイピングにはPythonを使う癖があるが、今ではそれが残念で、JSとLuaの間で悩んでいる

    • cgi削除の根拠はhttps://peps.python.org/pep-0594/#cgiにある
      興味深いことに、そこでは2000年7月14日、つまり25年前のhttps://peps.python.org/pep-0206/にリンクしており、その時点でもcgiパッケージを「設計が悪く、もはや修正はほぼ不可能」と表現していた
      https://github.com/jackrosenthal/legacy-cgiパッケージが標準ライブラリモジュールのドロップイン代替を提供しているようだ
    • Pythonメンテナーが削除しているのはcgiという名前のモジュールであって、CGIスクリプト実装のサポート自体ではない
      そのサポートはhttp.serverモジュールのCGIHTTPRequestHandlerに残っている
      cgiモジュールにあったのは、HTMLフォームデータをパースするいくつかの関数だけだった
    • Pythonが標準ライブラリからCGIを削除することに不満があるのは理解できる
      ただ、その次の代替として標準ライブラリ自体がないJSを検討するというのはよく分からない
      Luaにも標準ライブラリにCGIモジュールはない
    • かつて高性能Webは一種の技術的な芸術だった
      今では、速くリリースするために入れたとんでもなく無駄の多い部分を見つけ、それを止めることに近い
      きちんと試せば、アプリはストレージアクセス以外にはほとんどレイテンシを増やさないこともある
    • こういう場合は、JITが標準であるPHPやJSの方を好む
      Pythonは1.6から学んだが、主にOSスクリプティング用途で使ってきた
      1999〜2003年にApacheとIISモジュールでTclを使い、モジュールをCで書き直し続けながら、あまりにも多くの教訓を苦労して学んだ
  • 最近、350ドルのミニサーバーでGolangバイナリ、RabbitMQ、Redis、MySQLをすべて同じマシンに載せてテストしたところ、秒間5,000リクエストを継続処理できた
    24時間換算では4億リクエストになる
    最近の無料ツールがどれほど優れているかに驚くし、それでも私たちがクラウドプロバイダーにあれほど多くのお金を払っていることにも驚く
    完全に同じ比較ではないが、地下室の機材で全部を開発してチューニングする過程は本当に良かった

    • サーバーが秒間1リクエストよりはるかに多く処理できることを知らないまま、開発速度を10倍遅くするKubernetesマイクロサービス怪物をデプロイする例をあまりにも多く見てきた
      「Googleがそうしているから」以外に理由もなく、こうしたオーバーヘッドを負い続けるのは変だ
      私たちがうまく使っているモジュラーモノリスアーキテクチャについて、ぜひ記事を書かなければ
    • 必ずしもクラウドプロバイダーにお金を払う必要はない
      ホスティング会社で専用サーバーを借りて、好きなだけ使えばいい。ただし帯域幅や転送量の制限に大きく縛られる
      クラウドが使われるのは利害関係が多いからだ。たとえばVCや投資家がクラウド会社の株式も持っていて、実際には来ない想像上のトラフィック急増のせいで投資が台無しになるのを恐れている
      クラウド営業は投資家の不安を突くのがうまい
    • サイドプロジェクトを地下室でホストしようかと考えたが、そうすると停電やISP障害に弱くなる
      旅行中にドライブが故障したらどうしようもなく、うっかりロックアウトしてしまうとfallback用のシリアル端末もない
      r/homelabの人たちのように構成することもできるが、そうなると特に自分の時間も含めたとき、本当にお金を節約できているのかも不明になる
      結論として、クラウドプロバイダーは規模の経済のおかげでかなり良い取引だと思う
    • VMコストを見ると、高い理由は多くの計算資源ではなく、ローカルディスクが多いサーバーが必要だからだ
      奇妙なことに、片方だけを個別に得ることができない
      20TBドライブ4台とそこそこのCPUだけが付いていたら、どれほど大きな事業やサービスを運営できるか想像してみてほしい
      クラウドプロバイダーからそういう構成を得るのは簡単ではない
  • 「初期のWebコミュニティはこれが悪い考えだとすぐに学び、PHPのような技術を発明した」という部分は、厳密に言えば中核技術はmod_phpだった
    PHP自体は実行方式ではPerlと変わらなかったが、mod_perlと比べたmod_phpの設計上の選択のおかげで、PHPスクリプトはサーバーにそのまま放り込んでも高速に実行され、mod_perlは動かすには少し考えと魔法が必要だった

    • その頃、友人と一緒に、後に学習管理システムと呼ばれるようなものを開発していた
      コンテンツ管理、課題アップロード、イベントカレンダー、成績管理、リアルタイムチャット、フォーラムがあり、すべてCGI上の純粋なCだったので、作業は地獄のようだった
      PHPを知った日にほとんど涙が出そうになったのは、RFCを読んだりHTTPをリバースエンジニアリングしたりしながら一から苦労して組んでいたすべてが、PHPでは単なる関数呼び出しだったからだ
      もういい加減なurlencode実装をデバッグしたり、HTTPヘッダーの奇妙なキャリッジリターン1つのせいで1日を無駄にしたりしなくてよくなった
    • その通りだが、mod_phpはPHPエコシステムに初期に追加され、すぐに標準的な配布方式になった
      Apacheモジュールの最初のバージョンは、1996年のPHP/FI Version 2.0向けだったようだ: https://www.php.net/manual/phpfi2.php#module
  • cgi-bin が DB にアクセスする必要があるなら、プロセスが起動するたびに接続を開かなければならない
    FastCGI のようにコードをメモリ上に残しておくのは、起動時間のコストを避けるためだけではなく、DB 接続プール、少なくともスレッドごとの持続的接続を持てるからでもある

    • 規模が大きくなると、接続数のせいでデータベースが苦しくなる
      「Python はシングルスレッドだからたくさん立ち上げよう」と「Python は遅いからたくさん立ち上げよう」を同時にやったときがそうだった
      規模が大きくなると、結局 pgbouncer のような Python の外側にある共有接続プールと多くのチューニングで、DB を落とさずに負荷をさばけるようにしなければならない
      もちろん、その後ある程度の性能が出るマルチスレッド言語で再実装したら、また非常に単純になった
    • だから CGI は結局、リクエスト間で一部の状態を保持するモデルへと進化した
    • これを扱う標準的な方法もあった
      実質的にプロキシとして動作する別デーモンを立ち上げる方式だ
      TCP/IP の代わりに Unix ソケットを使えば、接続コストは比較的低くなる
    • UDP を使えばいい
  • このハードウェアで hello world アプリが秒間 2,400 リクエストなら、少し悪いのではないか
    性能を犠牲にして正確に何を得ているのかも分からない。コードがより単純になったわけでもない

    • 秒間 2,000 リクエストを超える必要がある場合にだけ悪い数値だ
      そういうサイトは全体の一部にすぎない
    • 素晴らしいわけではないが、多くのユースケースには十分だ
      HN のトラフィック爆弾にも耐えられそうだ
  • 昨日も議論されていた: https://news.ycombinator.com/item?id=44464272

  • 会社では、たまに素早く作る社内 Web アプリ用に、今でも cgi-bin ディレクトリを提供している
    単純に保てば使い勝手がよい
    CGI だからといって HTTP/1.0 を stdout に直接 print しなければならないという意味ではない
    例えば Python の組み込み wsgiref.handlers.CGIHandler を使えば、任意の WSGI アプリを CGI スクリプトとして実行できる
    import wsgiref.handlers, flask
    app = flask.Flask(name)
    wsgiref.handlers.CGIHandler().run(app)
    私たちはスクリプトを uwsgi とその CGI プラグイン[1]で実行している
    mod_cgi のために Apache や lighttpd を立ち上げるより、より単純で柔軟だと感じている
    uwsgi は systemd ユニットとして実行されるので、systemd の強化機能やサンドボックス機能もすべて活用できる
    uwsgi の CGI 処理で mod_cgi にはない便利な点は、特定のファイル形式に使うインタプリタを指定できることだ
    cgi = /cgi-bin=/webapps/cgi-bin/src
    cgi-allowed-ext = .py
    cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
    first byte までの時間は 250〜350ms で、私たちの用途には許容範囲だ
    [1]: https://uwsgi-docs.readthedocs.io/en/latest/CGI.html

    • 良い情報で、wsgiref.handlers.CGIHandler はまだ非推奨予定ではないようだ
  • 最近サイドプロジェクトで Apache を使ったが、理由の一つが .htaccess 機能だったので、似たような会話をした
    .htaccess ファイルをどこにでも置けば、Apache が各リクエストごとに追加のサーバー設定として読み込める: https://httpd.apache.org/docs/2.4/howto/htaccess.html
    これを避けていた大きな理由は性能だった。各リクエストごとに追加のディスクアクセスが必要で、可能ならメイン設定ファイルに入れるほうが常に良かった
    しかし今では、ほとんどのサーバーに SSD があり、Linux がファイルシステムキャッシュに使える余裕のある RAM もある可能性が高い
    もちろん Apache が設定を一度ではなく各リクエストごとにパースしなければならないので、性能は依然として少し悪くなる
    それでも、最近のほとんどのサーバー CPU がより強力であることを考えると、多くのユースケースでは受け入れられる
    サイドプロジェクトはごく初期版だが、すでに利用中だ: https://github.com/StaticPatch/StaticPatch/tree/main

    • Rasmus Lerdorf の言葉を引用すると:
      「私は本物のプログラマーではない。動くまであれこれ継ぎ足して、動いたら次へ進む。本物のプログラマーなら『なるほど、動いてはいるが、あちこちでメモリが漏れているね。直すべきでは?』と言うだろう。私は Apache を 10 リクエストごとに再起動すればいい。」
      PHP はその後非常に遠くまで来たが、その大きな部分は初期の失敗を正す過程だった
      「PHP 8 がはるかに良い理由は、私のコードがはるかに少なく入っているからだ。」
    • Apache がなぜファイルシステムを普通に監視しないのか理解できない
      この選択のせいで、HTTP リクエストの 99.99% が不要なディスク読み取りで遅くなる
  • ラピッドプロトタイピングのワークフローの一部として、これについてさらに考えてみた
    現代的な JIT 言語は、FastCGI モデルにしない限り、起動時間の大半が import に支配されそう
    ローカルスクリプトに h2o Web サーバーを使い始めてからそう思うようになった。mruby と FastCGI ハンドラーで設定ファイルをきれいかつ素早く書けるし、速度も非常に速い: https://h2o.examp1e.net/configure/fastcgi_directives.html
    もう一つ有用なのは、顧客がローカルソフトウェアを自分のコードで拡張できるようにする場合
    例えば AI ツールを拡張するために MCP を使わせる代わりに、特定のリクエスト構造を CGI として実装してもらうことができる

    • エンドユーザー環境で CGI プログラム向けの MCP フロントエンドを用意するのは悪くないアイデア
      MCP サービス自体も CGI で実装できるのではないかと気になってきた
      MCP フレームワークが、両方の実行モードをサポートするプログラムの形で機能を公開することもできそう
      仕様をもう少し掘ってみる必要がありそう
    • FastCGI は CGI の利点をほとんど失いがち
  • 自分にとっては inetd がまさに CGI だった
    おかげでインターネットがずっと面白くなった
    自分で inetd を使って複数のシェルスクリプトをホストしていたし、完全に Bash で書いた HTTP サーバーもあった
    その VPS はずいぶん前になくなり、当時はバージョン管理も使っていなかった
    それを書いたノートパソコンもなくなった
    それでも本当に楽しかった
    デプロイは Makefile + scp と同じくらい簡単で、テストは複数の netcat と grep を組み合わせた別の Bash スクリプトだった
    本当に良い時代だった