【入門編】Fiberを用いたリアルタイムデータストリーミング処理:WebSocketサーバーにおけるメッセージキューイングとFiberスケジューリング – PHPコア・内部エンジンと高速化・並行処理の極意解析バイブル

こんにちは。普段、Node.jsやGo、あるいはRustといった「非同期・並行処理が当たり前」の言語からPHPの世界を眺めていて、「PHPでリアルタイム性の高いWebSocketサーバーやストリーミング処理をやるのは、どうせプロセスが重いし無理なんだろうな」なんて壁にぶつかっていませんか?

もし、あなたがそう感じているなら、それはPHP 8.1以前の古い常識に縛られているだけかもしれません。

今回は、PHP 8.1で導入されたFiber(ファイバー)を主役に据え、イベントループと組み合わせることで、PHPのプロセスモデルを限界まで効率化し、数千のWebSocketコネクションを軽々とさばくメッセージングアーキテクチャの裏側を紐解いていきます。

ここを理解すると、「なぜPHPがWebの歴史を生き抜いてきたのか」、そして「モダンなPHPエンジンがどれほど洗練されているか」が綺麗に見えてきますよ。それでは、深淵なるZend VMの世界へ少しだけ足を踏み入れてみましょう。

—

1. PHPにおける「非同期」のパラダイムシフト:なぜFiberなのか?

これまでのPHP(特に一般的なFPM環境)は、「1リクエスト=1プロセス(または1スレッド)」の同期型モデルが基本でした。クライアントからリクエストが来ると、Zendエンジンがスクリプトを解釈し、DBや外部APIへのI/O(ネットワーク待ち)が発生するたびにプロセス全体がブロックされます。

これでは、数百・数千の常時接続を維持するWebSocketサーバーを構築しようものなら、OSのスレッドやプロセスが枯渇してしまいますよね。

コルーチン(協調的マルチタスク)という解決策

Node.jsは「イベントループ+コールバック(またはPromise)」でこれを解決しました。しかし、コールバック地獄やコードの可読性の低下という代償を払いました。

そこでPHP 8.1が導入したのが Fiber です。
Fiberは、「スタックを持つ協調的マルチタスク(Coroutine)」をPHPのコアに実装したものです。これにより、開発者は複雑なコールバックを書くことなく、「任意の場所で処理を中断(Suspend)し、後から再開(Resume)する」という非同期フローを、極めて自然な同期コードの見た目のままで記述できるようになりました。

—

2. 内部構造の覗き見:Zend VMとFiberのメモリ空間

少しだけ低レイヤの話をしましょう。
通常のPHP関数呼び出しは、コールスタック(Zend Execution Stack)を直線的に積み上げていきます。関数がネストすればするほどスタックフレームが深くなり、returnされると巻き戻されます。

しかし、Fiberが生成されると、PHPのCレベル(Zend VM)で独自の独立したコールスタックがヒープ上に割り当てられます。

[メインのコールスタック] <--- イベントループが稼働 | +---- Fiber::suspend() で一度処理を抜ける | v [Fiber内のコールスタック] <--- 非同期タスク(WebSocketメッセージ処理など)

  • ローカル変数
  • 実行ポインタ(opline)
  • 制御コンテキスト

つまり、I/O待ち(例えば、ソケットからのデータ読み込みやメッセージキューのポーリング)に直面したとき、Fiberは現在の実行コンテキスト(レジスタやコールスタックの状態)を保持したまま、親のイベントループへ制御を返します(Suspend)。イベントループはその間に別のクライアントの処理を進め、I/Oの準備ができたら再びFiberを再開(Resume)させます。

プロセスを増やさず、1つのプロセス・スレッド上で時分割にタスクを切り替える——これが、PHPで実現する非同期並行処理の正体です。

—

3. 実践:FiberとイベントループによるWebSocketメッセージング

理屈はこのあたりにして、実際にコードを見てみましょう。
ここでは、外部のイベントループライブラリ(ReactPHPやAmpなどの概念に近いシンプルな実装)を想定し、WebSocketのメッセージキューイングとFiberスケジューリングを行うコアロジックを構築します。

  • 簡易的なイベントループクラス
  • 実際の本番環境では ReactPHP や Revolt などの成熟したイベントループを利用します。
  • /
    class SimpleEventLoop
    {
    private \SplQueue $tasks;

    public function __construct()
    {
    $this->tasks = new \SplQueue();
    }

    // タスク(コールバック)をキューに追加
    public function addDeferred(callable $task): void
    {
    $this->tasks->enqueue($task);
    }

    // イベントループの駆動
    public function run(): void
    {
    while (!$this->tasks->isEmpty()) {
    $task = $this->tasks->dequeue();
    // タスクを実行
    $task();
    }
    }
    }

    /

    • WebSocketのクライアント接続とメッセージキューを管理するマネージャー

    /
    class WebSocketServerManager
    {
    private SimpleEventLoop $loop;
    private array $messageQueue = [];

    public function __construct(SimpleEventLoop $loop)
    {
    $this->loop = $loop;
    }

    /

    • クライアントからのメッセージを非同期に処理する(Fiberを使用)

    /
    public function handleClientMessage(string $clientId, string $payload): void
    {
    // 各クライアントのメッセージ処理をFiberとして独立させる
    $fiber = new \Fiber(function (string $id, string $data) {
    echo “[Fiber] クライアント {$id} からのメッセージ解析を開始…\n”;

    // 重いJSONパースやバリデーションをシミュレート
    \Fiber::suspend(‘parse_wait’);
    $decoded = json_decode($data, true, 512, JSON_THROW_ON_ERROR);

    echo “[Fiber] データベースへの非同期書き込みをシミュレート…\n”;
    // DBへのI/O待ちをシミュレート(ここで処理を一時中断)
    \Fiber::suspend(‘db_io_wait’);

    // キューへメッセージをブロードキャスト用に積む
    $this->messageQueue[] = [
    ‘sender’ => $id,
    ‘message’ => $decoded[‘text’] ?? ”,
    ‘time’ => time(),
    ];

    echo “[Fiber] クライアント {$id} の処理が完了しました。\n”;
    return “OK: {$id}”;
    });

    // Fiberを開始(最初のsuspendまで実行される)
    $status = $fiber->start($clientId, $payload);
    $this->processFiberState($fiber, $status);
    }

    /

    • Fiberの状態(Suspendされた理由)に応じてイベントループに再開タスクを登録する

    /
    private function processFiberState(\Fiber $fiber, mixed $suspendReason): void
    {
    if ($fiber->isTerminated()) {
    return;
    }

    // I/O待ちや重い処理のシミュレーション(タイマーや非同期ソケットの代わりに遅延実行)
    $this->loop->addDeferred(function () use ($fiber, $suspendReason) {
    echo “[EventLoop] 理由 ‘{$suspendReason}’ の待機が完了しました。Fiberを再開します。\n”;

    // 処理を再開し、次のsuspendまたは終了まで進める
    try {
    $status = $fiber->resume();
    if (!$fiber->isTerminated()) {
    $this->processFiberState($fiber, $status);
    }
    } catch (\Throwable $e) {
    echo “[Error] Fiber内で例外が発生: ” . $e->getMessage() . “\n”;
    }
    });
    }

    public function getQueue(): array
    {
    return $this->messageQueue;
    }
    }

    // ==========================================
    // 実行シミュレーション
    // ==========================================

    $loop = new SimpleEventLoop();
    $server = new WebSocketServerManager($loop);

    echo “=== WebSocketサーバーのモック起動 ===\n”;

    // クライアントAからのメッセージ受信
    $server->handleClientMessage(“client_A”, json_encode([“text” => “こんにちは、PHP Fiber!”]));

    // クライアントBからのメッセージ受信(イベントループ上で並行して扱われる)
    $server->handleClientMessage(“client_B”, json_encode([“text” => “非同期処理のテストです。”]));

    // イベントループを駆動させる
    $loop->run();

    echo “=== すべての処理が完了しました ===\n”;

    —

    4. このコードが教えてくれること:脳内トレースのポイント

    上記のスクリプトを実行すると、コンソールには次のような順序でログが出力されます。

    1. クライアントAのFiberが起動し、最初の `suspend(‘parse_wait’)` まで走って止まる。
    2. クライアントBのFiberが起動し、同じく最初の `suspend(‘parse_wait’)` まで走って止まる。
    3. イベントループが回り始め、Aのパース待ちが解除されて再開、次にDB I/O待ちで止まる。
    4. Bのパース待ちが解除され、同様に処理が進む。

    ここでのポイントは、「クライアントAのDB I/Oが終わるのを待っている間に、クライアントBのメッセージ解析が進んでいる」という点です。従来のPHPスクリプトであれば、AのDB書き込みが終わるまでBの処理は完全にブロックされていました。しかし、Fiberを使うことで、1つのプロセス内であたかもマルチスレッドのように処理がインターリーブ(交差)しています。

    アーキテクトからの実践的なアドバイス

    実際のプロダクション環境(例えば、Swoole, OpenSwoole, あるいは RevoltをベースにしたRatchetなど)では、このイベントループやソケットの監視はC拡張や底层の非同期エンジンが極めて高速に処理してくれます。

    私たちが書くべきアプリケーションコードは、このFiberの仕組みを前提とした「ブロッキングしない設計」を守ることです。コード内で安易に重い同期ブロッキング関数(例えば、従来の `sleep()` や、タイムアウトの長い同期HTTPクライアント)を呼んでしまうと、その瞬間に対象のFiberだけでなく、イベントループ全体の足を引っ張ることになります(これを「イベントループのブロック」と呼びます)。

    非同期の世界では、「待つときは、自ら進んで `suspend` し、準備ができたら `resume` してもらう」。この協調の精神を忘れないでください。

    —

    まとめ

    PHPにおけるFiberの導入は、単なる「新しい文法の追加」ではありません。それは、PHPという言語が長年抱えてきた「I/Oバウンドな処理への弱さ」というアーキテクチャ上の制約を、モダンなコルーチンモデルによって鮮やかに克服するための強力な武器です。

    「PHPは遅い、非同期が苦手」という先入観は、もう古いものになりつつあります。内部のZend VMがどうメモリを管理し、イベントループとFiberがどう協調しているのかを知れば、あなたの書くPHPコードは、他のどの言語にも劣らない洗練された非同期Webシステムへと生まれ変わるはずです。

    さあ、次のプロダクトでは、ぜひFiberを活用したリアルタイムストリーミングに挑戦してみてください。きっと、PHPの新しい可能性に驚くはずですよ。

    タイトルとURLをコピーしました