ハイブリッドPHPの台頭: PHPとGo、Rustの結合
(yekdeveloper.com)- 近年、PHPモノリシック構造の中でGoとRustを拡張言語として統合するハイブリッドな手法が注目されている
- 以前から、GoマイクロサービスとPHP 8.3モノリシックの組み合わせにより、生産性と高性能をバランスよく実現してきた
- パレートの法則(トラフィックの80%が20%のAPIに集中)に従い、ホットスポットとなるエンドポイントの最適化が必須であり、過去にはキャッシュやGoサービスの分離で対応していたが、複雑性が増していた
- 近年はPHPエコシステムの発展により、FFI、Rust拡張、**Go拡張(FrankenPHP)**といった手法が登場し、モノリシック内部で性能を大幅に高められるようになった
- Rust拡張はメモリ安全性と速度を同時に提供し、FrankenPHPはワーカーモードとGoベース拡張によって最大4倍以上の性能向上を示している
- 全体をGo/Rustで書き直すコストとリスクを避けつつ、ハイブリッドPHPというアプローチによって生産性と速度の両立が可能になる
背景と従来のアーキテクチャ
- 従来は、DDDモノリシックアプリケーション(mother)を中心に、特定機能の最適化のためGoベースのマイクロサービス(children)を別途開発していた
- Goマイクロサービスは高性能なトラフィック処理を担い、PHP 8.3モノリシックは小規模なバックエンドチーム環境で迅速な機能開発とデプロイの信頼性を提供していた
- この構成は、速度・安定性・生産性をすべて確保できるバランスポイントを提供していた
性能ボトルネックと従来の対応方法
- トラフィックの80%が20%のAPIエンドポイントに集中するというパレート原則がしばしば観測される
- 最も性能が重要なこの20%の領域に対して、最適化コードの作成、キャッシュ層の追加、Goマイクロサービスへの分離など、さまざまな方法が導入されてきた
- しかし、複雑性と運用負荷の面で限界があった
最新のPHPエコシステムにおけるハイブリッドオプション
- 最近では、PHPモノリシック内部で直接性能を改善できる技術が増えている
-
1. FFI (Foreign Function Interface)
- PHPのFFI機能により、PHPからCコードを直接呼び出せる
- システムレベルや性能クリティカルなロジックもPHPプロジェクト内で実装可能
- ただし、コンテキストスイッチのコストを考慮し、適切な場面に限って使うことが推奨される
-
2. Rustベース拡張
- Rust(またはZig)でPHP拡張を開発できる
- 高負荷な処理領域を、メモリ安全性とコンパイル性能を備えたRust拡張にオフロードすることで、信頼性と高速性の両方を確保できる
-
3. Goベース拡張: FrankenPHP
- 最近FrankenPHPへ移行し、worker modeで動作させたところ、従来比で4倍以上高速な性能が確認された
- 最近のリリースにより、GoでPHP拡張を書くことも可能になった
- これにより、GoのAPI性能をPHPモノリシック内でそのまま活用でき、言語分割なしに生産性と速度を結び付けることが可能になった
全面GoまたはRustへの完全移行ではない理由
- 全面的な書き直しコストとリスク負担が大きい
- すでに大規模で安定したアプリケーションを完全にGoやRustへ置き換えるのは、リスクもリソース消費も大きい
- PHP自体に依然として強みがある
- 大半の業務では、PHPの素早い開発、親しみやすいエコシステム、十分に速い性能が依然として競争力になる
- 真に性能限界が求められる一部の領域だけをGoやRustでハイブリッド構成にすれば、全面移行の必要性を解消できる
結論: ハイブリッドPHPの価値
- 現代のPHPエコシステムは、高速な開発生産性に加えて、高性能なC、Rust、Go拡張との連携オプションをすべて提供している
- このようなハイブリッド構造により、速度と生産性の両立が可能になる
- PHP中心の開発を維持しながら、必要に応じて言語を選択的に拡張できる新しいアーキテクチャパラダイムを提示している
5件のコメント
JavaScriptが変わっていくのと同じような感じになってきている気がします。
Node.jsにRustならまだしも……;; 私はPHPは
$のようなものをずっと使うのでコードを打つのが不便そうに見えるのですが、うまく使っている方々はあまり不便さを感じないものなのでしょうか?使いにくさを感じても、使っているうちにすぐ慣れてしまうものではないでしょうか?
人間は適応する動物です。
私はPHPの変数/関数という概念そのものに不便さを感じることはあっても、
$という表記法に不便さを感じたことはまったくありません。ドル記号のせいで使えない、~~ドル記号を使う連中だからお金をたくさん稼ぐ、米ドルではなくジンバブエ・ドルだからあまり稼げない~~ といった話は、もともと冗談として言われていたものではなかったでしょうか……
Hacker Newsの意見
私は汎用フレームワーク(Spring、Laravel、Phoenix など)にだんだん反感を持つようになった。最初は本当に生産的だが、レガシープロジェクトではいつも同じ問題が繰り返される。 プロジェクトごとにインフラ環境やビジネス条件が違うので、フレームワークの推奨どおりにだけ進めることはできず、あちこちに追加パッチや依存関係のハックが増えていく。 新しい言語やバージョンにアップグレードしようとすると、こうしたカスタマイズ部分がすべて壊れるので、結局は誰も更新せず、インフラ上でもはや動かせなくなる頃に涙の大移行をすることになる。 複数のライブラリを組み合わせて自分で抽象化を作るやり方は時間がかかるかもしれないが、部分的かつ柔軟にアップグレードでき、素早く方向転換できる。 Go のエコシステムはその点で理想的だと思うようになった。最初は違和感があったが、今ではむしろこのやり方のほうが好きになった。
Web フレームワークについては、「最初は本当に良いが、ある時点から不便になる」という感覚が強い。 シンプルなアプリを作るときは、Rails のような「15分でブログを作る」系の体験がまるで魔法のように感じられるが、複雑になるとフレームワーク自体がむしろ障害物になる。 個人的には、Express + Node.js や Vert.x + Java のような「中間レベル」の HTTP セットアップのほうが快適だ。
Python では microframework(Flask 系)と macroframework(Django)に分けて考えられる。 私はいつも Django を選ぶ。Flask はほとんど何も提案してくれないので、どのプロジェクトも毎回新しく飾り立てたスノーフレークになってしまう。 認証、テンプレート、Cookie、メールなど、N 個もの選択肢から何を選ぶかで意思決定疲れが起きる。 特にこうしたライブラリはたいてい個人開発なので、保守性やセキュリティ品質にもばらつきがある。 一方で Django は、ほとんどのプロジェクトが似た形になり、ほぼすべての基本機能をすぐに提供してくれる。 私があえて拡張ライブラリを使う理由があるのは、特殊な要件があるときだけだ。直接管理・検証されている部分が多いぶん、コードの信頼性とセキュリティも高いと思う。
Go に巨大なフレームワークがない理由は、言語の型システムがかなり未完成だからだ。 相互にうまく噛み合う複雑なライブラリ群を作るのが難しい。 私はジェネリクスが使えるようになるまで 9 年も待って、ようやく Go 向けのデータベースツールキットを最初に作った。 成功はしたが、以前 Java でこういうものを作っていたときのほうが良かったと感じるくらいだ。 結果型を別のジェネリック型へ map/filter/reduce できるようになれば、まったく別世界になるだろう。 union 型の指定機能さえあれば、any 型を使う必要もなくなるはずだ。 オーバーロードのサポートだけでもコードはずっときれいになるはずで、Go の型システムはまだ発展の余地がある。
私の分野では Go と Rust だけが有用だ。 意見の強いフレームワーク文化は肌に合わない。 Rails、Laravel、Django は、リレーショナル DB の CRUD 作業が明確な状況では素晴らしいと思う。
私はここ 5 年ほど PSR(Php Standards Recommendation)互換コンポーネントだけを使ってきた。フレームワークはまったく使っていない。 理由は、大規模フレームワークは結局長く使うと合わなくなるからだ。 制約が多く、管理と更新があまりにも大変になる。 会社向けの大規模プロジェクトでも個人サービスでも、PSR コンポーネント中心のアーキテクチャのほうが良いと感じる。
コードベースが巨大で、全体を書き直すのが不可能な場合に、ハイブリッドアプローチ(C、Rust、Go などの高性能言語と PHP の併用)に意味があるのは理解できる。 しかし、そうする必要さえないなら、C# API は開発速度と実行性能の両方を確保できたというのが私の経験だ。 C++ や Rust まで行く必要はほとんどなかった。 PHP も良いが、まだ型付き配列のようなものはない。 たとえば日付配列に文字列が来ても拒否しない言語だ。
C# を長く使ってきて、さまざまな Web/API フレームワークを経験している。 PHP は少し掘ってみると、Web 開発のための基本的な組み込み関数が本当に多くて良い。 欠点もあるが、何かを素早く作らなければならないときは、私の考えでは PHP が勝者という感じだ。
PHP が配列に間違った型(例: 日付配列に文字列)も許容してしまうという点はその通りだ。 たまに妙な動作やバグが飛び出してくる。 最近では、json_decode() でデコードしたとき、キー値が数字なら int に、それ以外は string になるので、キーの型が混在するバグに遭遇したことがある。 こうした細部は少し奇妙かもしれないが、それでも PHP 自体は本当に魅力的なフランケンシュタイン言語だ。
実際、静的解析器を使えばそうした型エラーは防げるものだ。 PHP へのジェネリクス対応も近いうちに入る可能性が高い。 関連情報は thephp.foundation ブログで見られる。
記事を書いた本人です。読んでくれてありがとう。 実際には全体を書き直す必要はなく、PHP を swoole や frankenphp のような worker ベースのランタイムで動かせば、Node レベルの性能が出せることもある。 型付き配列やジェネリクスの問題は phpstan のような静的解析器がサポートしており、型注釈を活用すれば型安全性もかなり向上させられる。
私は VB6 のサポート終了以降、Microsoft 言語は一切使わないことにした。 オープンソース言語だけが精神衛生に良い選択だ。
私が {{company}} に入社したとき、全社の PHP 環境は 5.4 で、その頃は PHP への嫌悪感があふれていた。 しかし最新の PHP を経験してみると、今まさに PHP から抜け出そうとしているこの時点で、むしろこうした移行が「今となっては後退」にすら感じられるほどだ。 いまだに皆が PHP を 5.x 時代のものとして判断しているが、今はまったく違う。
私は少し違う見方をしている。採用市場を見ると、いまだに PHP に対する印象(良くも悪くも)があり、優秀な開発者を採る際の制約になっている。 技術的に PHP が以前ほど悪くないとしても、企業ブランディングの観点では PHP から離れることにはまだ意味があると感じる。
「PHP は今や awesome だ」という言い方には同意できない。確実に良くはなったが、awesome という表現は少し大げさだ。
PHP はどんどん良くなっている。近いうちにもう少し深く見てみるべきだと思っている。
私たちも 4 年前にまったく同じ悩みを抱え、結局 PHP 8 にアップグレードして使い続けている。 その選択はここ数年、私たちのチームにはうまく合っている。
Pasir は frankenphp に似ているが Rust ベースだ。非常に有望だが、まだ開発初期だ。
Pasir github Pasir は Zend API バインディングを Rust に変換したものを使っている。
Zend API Rust バインディング ngx-php のような興味深い実験もある。nginx バイナリ内部に Zend API で PHP を埋め込んだ構造だ。
ngx-php github workerman は asio ハイブリッドバックエンドを使って非常に高速なランタイムを実現している。
workerman github
Pasir や frankenphp などが既存の PHP モジュールもサポートしているのか気になる。
おすすめありがとう。本当にすごそうだ。 ただ、言うとおりまだ本番レベルには遠いという点には同意する。 私たちは php foundation が支援している frankenphp を最終的に選んだ。
デバッグや保守の複雑さが見過ごされがちだが、選べるのであればこうした組み合わせを選ぶのは避けたほうがいいと思う。
私も自分のアプリを PHP から Go に作り直したが、それは会社にとって成功した投資だった。 PHP の 2 万行のコードを Go では 4 千行に減らせ、効率も大きく向上できた。 PHP の会社なら、いっそ大規模な全面書き換えを計画し、テスト追加(Go のほうが簡単)とあわせて実行してみることを強く勧める。 Rust/PHP や Go/PHP のように複数言語を混ぜて保守で苦しむよりはましだと思う。
PHP から Go に移して、どうしてコード行数がそこまで減ったのか気になる。 Go はかなり冗長な言語だと思っている。私の経験では PHP は中程度で、Haskell が最も凝縮的、Java/Go はエラー処理などの理由でむしろ長くなりがちだった記憶がある。
PHP から Go への移行で行数が 5 分の 1 になったというのは納得しにくい。 PHP にはさまざまな省略構文があるが、Go にはそういうものがあまりないと感じていた。
リライトによって性能と効率が良くなったとしても、それが「言語のおかげなのか、新たに反映したアーキテクチャ改善のおかげなのか」は常に重要なポイントだと思う。
Go へのリライトでは if err != nil パターンにまみれて、むしろコードが 10 倍に膨らむのではと思っていた。 私は Python へのリライトを経験したが、Python もコードがかなり冗長になり、依存性注入のようなパターンがテストで煩雑だった。
私は無条件でリライトを勧めるわけではない(自分では 2 回うまくやり切った経験があるが、それでも自分では選ばないと思う)。 最近の PHP ランタイムは本当に速くなっていて、試す価値も大きい。 特に swoole のような作業キャッシュを活用すれば、Go と同じくらい速い場合もある(ベンチマーク参照推奨)。
ときどき、私たちは本当に基本に忠実であるべきだと思う。つまり、ピクセル、データ、レイテンシ/帯域幅だ。 Web も結局は「正しいピクセルを、人間の目に十分速く、ネットワーク資源の範囲内で描く最適化問題」だと思っている。 「ユーザーがこれから見るピクセルは何か?」「それを描くために必要なデータは何か?」「今後使いそうなデータは先にプリフェッチすべきか?」といった考え方で取り組むのが正しいと感じる。
alumina-ui を WASM 向けの egui で作っているのだが、 HTML や JavaScript、CSS などの複雑な Web 知識なしに、ブラウザに合ったサイズのキャンバスを 1 つ用意して、あとはそのまま WebGL でレンダリングすればいい。 自分の好きな言語で高速かつ GL アクセラレーションされたグラフィックスを出せるので本当に便利だ。 WASM/WebGL はこうした抽象化のおかげでとても気に入っている。
ユーザーが見るピクセルだけに集中するのは、あまりに断片的だ。 ソフトウェアプロジェクトでは、即時の UX だけでなく開発時間も最適化しなければならない。 最初の画面が表示されるまでの遅延と、実際の開発工数は決して比例しない。
FrankenPHP はとても面白そうに見えるが、実際には少し変わっている。 PHP モジュールなしで PHP を使う人はいないし、FrankenPHP がサポートする PHP モジュールの一覧も明確ではない。自分が欲しい追加モジュールをビルドして組み込めるのかどうかも不明だ。 Caddy と密接に結びついているが、私はこの Web サーバーに慣れておらず、nginx のほうを好む。 ガイドがないので、nginx で php-fpm の代替として使えるのかどうかも分からない。 さらに Caddy や FrankenPHP の Docker イメージは Let's Encrypt 証明書しか想定していないようで、自前で SSL を構成したり HTTP のみで運用したりする場合は本当に直感的ではない。
PHP モジュールの問題は、たいてい Dockerfile 内で自分でビルドする。 例として pgsql モジュールを追加するなら、apt で依存関係を入れ、docker-php-ext-install でモジュールを入れ、終わったら依存関係を削除・掃除する形だ。 HTTP 設定も Caddyfile でポート 80 をそのまま開けばよい。
static ビルドには、基本的に数十個の主要な PHP モジュールが同梱されている。 詳しいモジュール一覧とビルドスクリプトは frankenphp build-static.shで見られる。
どのようなワークロードで C/Rust/Go のような言語拡張が本当に必要になるのか気になる。 そういうケースがあることは理解できるが、なぜこうした複雑さをスタックに追加しなければならないのか、また別の方法では解決できないのかももっと知りたい。
PHP でいちばん嫌いなのは、すべての HTTP リクエストごとにアプリケーション全体が毎回ブートストラップされ、オートロードや設定の再評価が行われることだ。 もちろんキャッシュなどはあるが、Go のようにエンジンが常駐している方式と比べると、やはり納得しがたい。
reactphp.org
php.net ev module
pecl-event
workerman.net
frankenphp worker ドキュメント
私はむしろ、この点こそが PHP の最大の利点だと思っている。 スケールアウトがものすごく簡単になる。
私はむしろこういう構造が好きだ。 本質的に状態が最小化される(ある程度までは)。
君の言うとおりだ。この方式は本当にひどい。 特に PHP が独自テンプレート言語のように使われるときはなおさらだ。 これを解決しようとするカスタムテンプレートエンジンや常駐ランタイムも、結局は「豚に口紅を塗る」ようなものにすぎない。 最初から Personal HomePage なんて名前ではなかった言語を選ぶべきだった。