PHPの並列処理について

採用はこちら

PHPの並列処理とは、複数の処理を同時進行させることで、処理時間の短縮や待ち時間の削減を目指す考え方です。

ただし、PHPで「並列処理」と呼ばれるものには、厳密にはいくつかの種類があります。

複数のCPUコアやプロセスを使って本当に同時実行するものもあれば、外部API通信やファイルI/Oの待ち時間を効率よく扱うものもあります。

そのため、PHPの並列処理を理解するには、まず 並列処理・並行処理・非同期処理・バックグラウンド処理 の違いを押さえることが重要です。

PHPで扱う「並列処理」は広い意味で使われる

PHPで「並列処理」と言う場合、実務では次のような処理をまとめて指すことがあります。

種類意味PHPでの代表例
並行処理複数の処理を同時進行のように扱うcurl_multi_exec、ReactPHP、Amp、Fiber
並列処理複数CPU・複数プロセス・複数スレッドで本当に同時実行するpcntl_forkparallel拡張、複数ワーカー
非同期処理処理の完了を待たずに別の処理へ進むReactPHP、Amp、Swoole、非同期HTTP
バックグラウンド処理ユーザーのリクエストとは切り離して裏側で実行するLaravel Queue、cron、Supervisor

厳密に言えば、curl_multi_exec やFiberはCPUを使って本当に同時実行する「狭い意味での並列処理」ではありません。

しかし、PHPで複数の処理を効率よく扱う方法として、並列処理の文脈で一緒に説明されることが多いです。

PHPで並列処理が必要になる場面

PHPで並列処理や非同期処理が必要になるのは、主に次のような場面です。

用途具体例
外部API連携複数の広告API、決済API、SNS APIを同時に取得する
大量データ処理CSVインポート、ログ集計、データ変換
画像・動画処理画像リサイズ、WebP変換、サムネイル生成
メール・通知送信大量メール、プッシュ通知、Webhook送信
スクレイピング複数URLへの同時アクセス
レポート生成PDF生成、管理画面用レポート作成
リアルタイム通信チャット、通知、WebSocket

たとえば、外部APIを5つ順番に呼び出す場合、1つのAPIに2秒かかると合計で約10秒かかります。

一方、複数のAPIを同時進行で呼び出せば、全体の処理時間を短縮できる可能性があります。

目次

PHPの並列処理を理解するための基本

PHPで並列処理を考えるときは、いきなり実装方法を選ぶのではなく、まず処理の種類を見極めることが大切です。

特に重要なのが、CPUバウンドI/Oバウンド の違いです。

CPUバウンドとは

CPUバウンドとは、CPUの計算能力がボトルネックになる処理のことです。

たとえば、次のような処理が該当します。

画像変換
動画変換
大量計算
PDF生成
暗号化
巨大CSVの集計

このような処理はCPUを使うため、単に非同期にするだけでは速くならないことがあります。

処理時間を短縮したい場合は、複数プロセス、複数ワーカー、parallel拡張、バッチ分割などを検討します。

I/Oバウンドとは

I/Oバウンドとは、通信や読み書きの待ち時間がボトルネックになる処理のことです。

たとえば、次のような処理です。

外部API通信
HTTPリクエスト
DBアクセス
ファイル読み書き
S3などの外部ストレージ操作
メール送信

このような処理では、CPUが忙しいというより、外部サービスやディスクの応答を待っている時間が長くなります。

そのため、curl_multi_exec、非同期HTTPクライアント、キュー、バルク処理などが有効です。

並列化すれば必ず速くなるわけではない

並列処理は便利ですが、必ずしも処理が速くなるとは限りません。

たとえば、DBがボトルネックになっている処理でワーカー数を増やすと、同時接続数やロックが増えて、かえって遅くなることがあります。

また、外部APIに大量のリクエストを同時に送ると、レート制限に引っかかる可能性もあります。

そのため、並列処理を導入する前に、次の点を確認することが重要です。

CPUがボトルネックなのか
I/O待ちがボトルネックなのか
DB接続数は足りているか
外部APIのレート制限は問題ないか
メモリ使用量は増えすぎないか
失敗時に再実行できるか

PHPで並列処理を実現する主な方法

PHPで並列処理や非同期処理を実現する方法には、いくつかの選択肢があります。

それぞれ得意な用途が違うため、目的に合わせて使い分けることが大切です。

curl_multi_execで複数のHTTP通信を並行処理する

curl_multi_exec は、複数のHTTPリクエストを同時進行で処理したい場合に使えるPHPの機能です。

外部APIを複数同時に呼び出したい場合や、複数のURLからデータを取得したい場合に向いています。

ただし、curl_multi_exec はCPU処理を複数コアで同時実行する仕組みではありません。

正確には、複数のHTTP通信を並行・非同期的に扱うための方法です。

curl_multi_execが向いている処理

curl_multi_exec は、次のような処理に向いています。

複数の外部APIを同時に呼び出す
複数URLをスクレイピングする
複数の画像URLから画像を取得する
複数のWebhookを送信する
広告媒体ごとのAPIレポートを同時取得する

たとえば、Google Ads、Meta広告、Yahoo広告、TikTok広告など、複数の広告APIからレポートを取得する場合、順番に処理すると時間がかかります。

curl_multi_exec を使えば、複数のHTTP通信を並行して進められるため、待ち時間の短縮が期待できます。

curl_multi_execのサンプルコード

以下は、複数URLへ同時にリクエストする基本的なサンプルです。

<?php

$urls = [
    'https://example.com/api/1',
    'https://example.com/api/2',
    'https://example.com/api/3',
];

$multiHandle = curl_multi_init();
$handles = [];

foreach ($urls as $url) {
    $ch = curl_init();

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_CONNECTTIMEOUT => 3,
    ]);

    curl_multi_add_handle($multiHandle, $ch);
    $handles[] = $ch;
}

$running = null;

do {
    $status = curl_multi_exec($multiHandle, $running);

    if ($status > CURLM_OK) {
        break;
    }

    curl_multi_select($multiHandle);
} while ($running > 0);

$responses = [];

foreach ($handles as $ch) {
    $responses[] = [
        'body' => curl_multi_getcontent($ch),
        'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE),
        'error' => curl_error($ch),
    ];

    curl_multi_remove_handle($multiHandle, $ch);
    curl_close($ch);
}

curl_multi_close($multiHandle);

print_r($responses);

このコードでは、複数のURLに対してほぼ同時にリクエストを送り、最後にレスポンスをまとめて取得しています。

curl_multi_execを使うときの注意点

curl_multi_exec を実務で使う場合は、次の点に注意が必要です。

HTTPステータスコードを確認する
cURLエラーを確認する
タイムアウトを設定する
同時接続数を増やしすぎない
外部APIのレート制限を守る
失敗時のリトライ処理を用意する

レスポンスが返ってきたからといって、処理が成功したとは限りません。

たとえば、通信自体は成功していても、API側が次のようなステータスを返す場合があります。

400 Bad Request
401 Unauthorized
403 Forbidden
429 Too Many Requests
500 Internal Server Error
503 Service Unavailable

そのため、curl_multi_exec の戻り値だけでなく、個別リクエストごとのHTTPステータスやエラー内容を確認する必要があります。

pcntl_forkでマルチプロセス処理を行う

pcntl_fork は、PHPで子プロセスを作成するための関数です。

親プロセスから子プロセスを生成し、それぞれのプロセスで別々の処理を実行できます。

CLIバッチや大量データ処理など、Webリクエストとは切り離した処理で使われることがあります。

pcntl_forkはCLI向けの機能

pcntl_fork は、基本的にCLIスクリプト向けの機能です。

通常のWebアプリケーションで、PHP-FPMやApache上のリクエスト処理中に使う設計は避けた方が安全です。

Webアプリで重い処理を並列化したい場合は、pcntl_fork よりも、キューやワーカーを使う方が実務的です。

pcntl_forkのサンプルコード

以下は、複数の子プロセスを作成して処理する例です。

<?php

$tasks = [
    'task 1',
    'task 2',
    'task 3',
    'task 4',
];

$children = [];

foreach ($tasks as $task) {
    $pid = pcntl_fork();

    if ($pid === -1) {
        exit('fork failed');
    }

    if ($pid === 0) {
        // 子プロセス側
        echo "Start: {$task}\n";

        sleep(3);

        echo "End: {$task}\n";

        exit(0);
    }

    // 親プロセス側
    $children[] = $pid;
}

foreach ($children as $childPid) {
    pcntl_waitpid($childPid, $status);
}

echo "All tasks finished\n";

このコードでは、タスクごとに子プロセスを作成し、それぞれの子プロセスが独立して処理を行います。

pcntl_forkが向いている処理

pcntl_fork は、次のような処理に向いています。

CLIバッチ
大量CSVの分割処理
画像変換
動画変換
大量データの集計
スクレイピングバッチ
一括処理用の管理コマンド

ただし、プロセス管理、ログ管理、エラー処理、結果集約を自分で設計する必要があります。

pcntl_forkの注意点

pcntl_fork を使うと、親プロセスのメモリ空間が子プロセスにコピーされます。

ただし、子プロセスで変更した変数は、基本的に親プロセスには反映されません。

<?php

$count = 0;

$pid = pcntl_fork();

if ($pid === 0) {
    $count++;
    echo $count . PHP_EOL; // 1
    exit;
}

pcntl_wait($status);

echo $count . PHP_EOL; // 0

子プロセスで $count を増やしても、親プロセスの $count は増えません。

結果を親プロセス側で集約したい場合は、次のような方法を使います。

一時ファイルに書き出す
DBに保存する
Redisに保存する
ソケットやパイプで通信する
メッセージキューを使う

また、fork前に作成したDB接続やソケットを、親子プロセスでそのまま共有する設計は避けるべきです。

fork後に各プロセスで接続を作り直すなど、接続管理を明確にする必要があります。

parallel拡張で並列実行する

parallel は、PHPで並列実行を行うための拡張機能です。

parallel\Runtime を使うことで、処理を別のRuntimeで実行し、結果を parallel\Future から取得できます。

CPUを使う重い処理を並列化したい場合に候補になります。

parallelのサンプルコード

<?php

$runtime1 = new \parallel\Runtime();
$runtime2 = new \parallel\Runtime();

$future1 = $runtime1->run(function () {
    sleep(2);
    return 'result 1';
});

$future2 = $runtime2->run(function () {
    sleep(2);
    return 'result 2';
});

echo $future1->value() . PHP_EOL;
echo $future2->value() . PHP_EOL;

この例では、2つの処理を別々のRuntimeで実行し、value() で結果を取得しています。

parallelが向いている処理

parallel は、次のような処理に向いています。

CPUを使う計算処理
画像処理
データ変換
重いCLIツール
複数タスクの同時実行

parallelの注意点

parallel は便利ですが、導入ハードルはやや高めです。

特に重要なのは、ZTS版PHPが必要になる点です。

ZTSとはZend Thread Safetyのことで、PHPをビルドするときに有効化する必要があります。

一般的なPHP-FPM環境やレンタルサーバーでは、ZTS版PHPや parallel 拡張を自由に導入できないことが多いため、実務では使える環境が限られます。

そのため、導入可否は次のように考えるとよいでしょう。

環境parallelの導入しやすさ
自前サーバー検討しやすい
Docker環境検討しやすい
VPS構成次第で可能
共有レンタルサーバー難しいことが多い
一般的なPHP-FPM環境そのままでは難しいことが多い

また、parallel は通常の共有メモリ型のスレッドプログラミングとは異なります。

PHPの変数を複数の実行単位で自由に共有し、同時に書き換えるような設計ではありません。

Fiberで処理を中断・再開する

PHP 8.1からはFiberが使えます。

Fiberは、処理を途中で中断し、後から再開できる仕組みです。

非同期処理やイベントループを実現するための土台として使われることがあります。

Fiberは並列処理そのものではない

Fiberは、複数の処理をCPU上で本当に同時実行する仕組みではありません。

Fiberを使ったからといって、それだけで処理が速くなるわけではありません。

Fiberは、イベントループや非同期I/Oライブラリと組み合わせることで、待ち時間を効率よく扱うための仕組みです。

そのため、PHPの並列処理を解説する記事では、Fiberを「並列処理の方法」としてではなく、「非同期処理を支える仕組み」として説明する方が正確です。

Fiberのサンプルコード

<?php

$fiber = new Fiber(function () {
    echo "A\n";

    Fiber::suspend();

    echo "B\n";
});

$fiber->start();

echo "C\n";

$fiber->resume();

出力は次のようになります。

A
C
B

この例では、Fiber内の処理が Fiber::suspend() で一度止まり、その後 resume() によって再開されています。

Fiberが使われる場面

Fiberは、次のような仕組みの内部で使われることがあります。

非同期HTTPクライアント
非同期DBクライアント
イベントループ
コルーチン的な処理
非同期フレームワーク

ただし、Fiber単体でCPU負荷の高い処理を高速化できるわけではありません。

ReactPHPでイベント駆動の非同期I/Oを扱う

ReactPHPは、PHPでイベント駆動・非同期I/Oを扱うためのライブラリです。

イベントループを中心に、HTTP通信、TCP通信、ストリーム処理、タイマー処理などを扱えます。

ReactPHPが向いている処理

ReactPHPは、I/O待ちが多い処理に向いています。

非同期HTTPクライアント
WebSocketサーバー
常駐型TCPサーバー
リアルタイム処理
外部通信が多い処理
大量の接続を扱う処理

一方で、画像変換や大量計算のようなCPUバウンド処理を速くしたい場合、ReactPHP単体では不十分です。

その場合は、別プロセス化、キュー、ワーカー、parallel などと組み合わせる必要があります。

ReactPHPのサンプルコード

<?php

require 'vendor/autoload.php';

$loop = React\EventLoop\Loop::get();

$loop->addTimer(1.0, function () {
    echo "1秒後に実行\n";
});

$loop->addTimer(2.0, function () {
    echo "2秒後に実行\n";
});

$loop->run();

ReactPHPでは、通常のPHPスクリプトのように上から下へ実行して終了するのではなく、イベントループが処理を管理します。

キューとワーカーでバックグラウンド処理を行う

Webアプリケーションで最も実務的な方法のひとつが、キューとワーカーを使う方法です。

重い処理をWebリクエスト中に実行するのではなく、キューに登録し、裏側のワーカーが処理します。

キューの基本的な仕組み

キューを使った処理は、次のような流れになります。

ユーザーがフォーム送信
↓
重い処理をキューに登録
↓
ユーザーにはすぐレスポンスを返す
↓
裏側のワーカーがジョブを処理する
↓
処理結果や進捗をDBに保存する

この設計にすると、ユーザーを長時間待たせずに済みます。

また、複数のワーカーを起動すれば、複数のジョブを並列に処理できます。

キューが向いている処理

キューは、次のような処理に向いています。

メール送信
画像リサイズ
PDF生成
CSVインポート
外部API連携
動画変換
通知送信
レポート生成

Webアプリでは、pcntl_forkparallel を使ってリクエスト中に無理やり並列化するよりも、キューに逃がす方が安全で保守しやすいです。

Laravel Queueの例

Laravelでは、ジョブをキューに登録して非同期処理できます。

<?php

ProcessCsvImport::dispatch($filePath);

return response()->json([
    'message' => 'CSVインポートを開始しました',
]);

ワーカー側では、次のコマンドでジョブを処理します。

php artisan queue:work

複数のワーカーを起動すれば、複数ジョブを並列に処理できます。

php artisan queue:work
php artisan queue:work
php artisan queue:work

本番環境では、Supervisor、systemd、Docker、Kubernetesなどを使ってワーカーを管理するのが一般的です。

キューを使うときの注意点

キューそのものが自動的に処理を並列化するわけではありません。

実際には、複数のワーカープロセスが同じキューからジョブを取り出して処理することで、並列処理が実現されます。

そのため、ワーカー数を増やすと、次のような負荷も増えます。

DB接続数
外部APIへのアクセス数
メモリ使用量
CPU使用率
ファイルI/O

ワーカー数は、最初から大きくしすぎず、負荷を見ながら調整することが大切です。

SwooleやOpenSwooleで高性能な非同期処理を行う

SwooleやOpenSwooleは、PHPで高性能な非同期処理や常駐型サーバーを実現するための拡張です。

HTTPサーバー、WebSocket、コルーチン、プロセスプール、タスクワーカーなどの機能を備えています。

SwooleやOpenSwooleが向いている処理

SwooleやOpenSwooleは、次のような用途に向いています。

高負荷なAPIサーバー
WebSocketサーバー
リアルタイム通知
チャットアプリ
常駐型ワーカー
大量接続を扱うサービス

通常のPHP-FPMとは異なり、PHPアプリケーションを常駐プロセスとして動かす設計になります。

SwooleやOpenSwooleの注意点

SwooleやOpenSwooleは強力ですが、従来のPHP-FPM型の実行モデルとは大きく異なります。

通常のPHPでは、1リクエストごとにPHPスクリプトが実行され、処理が終わると状態も破棄されます。

一方、SwooleやOpenSwooleでは、PHPプロセスが常駐します。そのため、次の点に注意が必要です。

グローバル変数の扱い
シングルトンの状態
DB接続の管理
メモリリーク
リクエスト間で残る状態
長時間稼働時の安定性

導入する場合は、PHP-FPMとは別の設計思想が必要です。

目的別に見るPHP並列処理の選び方

PHPで並列処理を実装する場合、目的に応じて適切な方法を選ぶ必要があります。

外部APIを複数同時に呼び出したい場合

外部APIを複数同時に呼び出したい場合は、次の方法が候補になります。

curl_multi_exec
Guzzle Pool
ReactPHP
Amp
キュー

短時間で終わるAPI通信であれば、curl_multi_exec や Guzzle Pool が使いやすいです。

一方、処理に時間がかかる場合や、API取得後に重いデータ処理がある場合は、キューに分ける方が安全です。

CSVや大量データを処理したい場合

CSVや大量データを処理する場合は、キューとチャンク処理が実務的です。

CSVをアップロード
↓
ファイルを保存
↓
1000行や1万行ごとに分割
↓
チャンクごとにジョブを登録
↓
複数ワーカーで処理
↓
進捗をDBに保存

大量データを1回のWebリクエストで処理しようとすると、タイムアウトやメモリ不足が起きやすくなります。

そのため、Webリクエスト中には処理を開始するだけにして、実際の処理はワーカーに任せる設計が適しています。

画像処理を高速化したい場合

画像処理では、CPUとメモリを多く使うことがあります。

そのため、次のような方法が候補になります。

キュー
複数ワーカー
pcntl_fork
parallel

画像1枚ごとにジョブを分け、複数ワーカーで処理する設計が実務では扱いやすいです。

ただし、ワーカー数を増やしすぎると、CPUやメモリを圧迫します。

最初は少ない並列数から始め、サーバー負荷を確認しながら調整します。

メール送信を行いたい場合

メール送信は、キューで処理するのが基本です。

ユーザーの画面表示をメール送信完了まで待たせる必要はありません。

ユーザー登録
↓
メール送信ジョブを登録
↓
画面にはすぐレスポンス
↓
ワーカーがメールを送信

大量メールを送る場合は、メール配信サービスのレート制限や送信上限にも注意が必要です。

スクレイピングを高速化したい場合

スクレイピングでは、curl_multi_exec、Guzzle Pool、ReactPHPなどが候補になります。

ただし、並列数を増やしすぎると、相手サーバーに負荷をかけたり、アクセス制限を受けたりする可能性があります。

実務では、次の制御が必要です。

同時接続数の上限
リクエスト間隔の調整
タイムアウト設定
リトライ制御
User-Agent設定
robots.txtや利用規約の確認
エラー時のバックオフ

リアルタイム通信を行いたい場合

チャット、通知、WebSocket、リアルタイムダッシュボードなどを実装したい場合は、次のような方法を検討します。

ReactPHP
Swoole
OpenSwoole
Ratchet
Laravel Reverb
Node.jsとの分担

通常のPHP-FPMだけでリアルタイム通信を実装するのは難しいため、常駐型サーバーや専用の仕組みを使う方が適しています。

PHPで並列処理を実装するときの注意点

PHPで並列処理を導入すると、処理時間を短縮できる可能性があります。

一方で、設計を誤ると、エラーや負荷の問題が増えることがあります。

並列数を増やしすぎない

並列数を増やせば、必ず速くなるわけではありません。

たとえば、100件のAPIリクエストを一気に投げると、次のような問題が起きる可能性があります。

外部APIのレート制限に引っかかる
自サーバーのメモリ使用量が増える
DB接続数が増えすぎる
ネットワーク帯域を圧迫する
エラー率が上がる

最初は同時実行数を少なめに設定し、ログやメトリクスを見ながら調整することが大切です。

DB接続数に注意する

並列処理では、複数のプロセスやワーカーが同時にDBへ接続することがあります。

ワーカー数を増やすと、DB接続数も増えやすくなります。

対策としては、次のような方法があります。

ワーカー数を制限する
DB接続を適切に閉じる
長いトランザクションを避ける
バルクインサートを使う
ロック範囲を小さくする
クエリを最適化する

DBがボトルネックになっている場合は、並列化よりもクエリ改善やインデックス設計を優先した方がよいこともあります。

ファイル書き込みの競合に注意する

複数のプロセスが同じファイルに同時に書き込むと、ファイルの内容が壊れる可能性があります。

対策としては、次のような方法があります。

プロセスごとに別ファイルへ書き込む
最後にファイルをマージする
flockを使う
DBやRedisに保存する
一時ファイル名をユニークにする

並列処理では、同じリソースへ同時アクセスする可能性を常に考える必要があります。

ログを分かりやすく残す

並列処理では、複数の処理が同時に進むため、ログが混ざりやすくなります。

どのジョブで何が起きたのかを追跡できるように、ログには次の情報を含めるとよいでしょう。

job_id
worker_id
process_id
対象データID
開始時刻
終了時刻
処理時間
エラー内容
リトライ回数

ログが不十分だと、失敗したジョブの原因調査が難しくなります。

リトライ設計を用意する

並列処理では、一部の処理だけ失敗することがあります。

たとえば、100件のAPIリクエストのうち、3件だけタイムアウトするケースです。

その場合、全体を失敗扱いにするのではなく、失敗したものだけ再実行できる設計が重要です。

成功済み
失敗
リトライ待ち
リトライ上限超過
手動確認待ち

このように状態を管理しておくと、障害時にも復旧しやすくなります。

タイムアウトを必ず設定する

外部通信を並列処理する場合、タイムアウト設定は必須です。

<?php

curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);

タイムアウトがないと、1つの通信が詰まっただけで全体の処理が止まる可能性があります。

API通信、DB通信、ファイル操作など、待ち時間が発生する処理には適切なタイムアウトを設定しましょう。

メモリ使用量に注意する

並列処理では、同時に複数のデータを扱うため、メモリ使用量が増えやすくなります。

大量データを処理する場合、すべてを一度にメモリへ載せるのは避けるべきです。

悪い例は次のような処理です。

<?php

$allRows = loadHugeCsv();

大量CSVを一括で配列に読み込むと、メモリ不足になる可能性があります。

より安全なのは、1行ずつ、または一定件数ごとに処理する方法です。

<?php

$handle = fopen('large.csv', 'r');

while (($row = fgetcsv($handle)) !== false) {
    process($row);
}

fclose($handle);

大量データは、ストリーム処理やチャンク処理を使って扱うのが基本です。

冪等性を意識する

並列処理では、同じジョブが複数回実行される可能性があります。

たとえば、ワーカーが途中で落ちた場合や、タイムアウト後にリトライされた場合、同じ処理が再度実行されることがあります。

このとき、同じ処理が2回実行されても問題が起きない設計が重要です。

これを 冪等性 といいます。

たとえば、メール送信ジョブで同じメールが2回送られると困る場合は、次のような対策を行います。

送信済みフラグを持つ
ユニークキーを設定する
処理前に状態を確認する
トランザクションを使う
ジョブIDを記録する

並列処理では、速さだけでなく、失敗時や再実行時の安全性も重要です。

実務でおすすめの構成

PHPで並列処理を導入する場合、アプリケーションの規模や用途に応じて構成を変えるとよいでしょう。

小規模なPHPアプリの場合

小規模なPHPアプリでは、まずシンプルな構成から始めるのがおすすめです。

外部APIの並行取得 → curl_multi_exec
定期処理 → cron + CLI
重い処理 → バッチ処理
メール送信 → キューまたはメール配信サービス

いきなり複雑な非同期フレームワークを導入するより、必要な部分だけを並行化する方が保守しやすくなります。

Laravelアプリの場合

Laravelを使っている場合は、Queueを中心に設計するのが実務的です。

重い処理 → Laravel Queue
複数処理 → 複数ワーカー
定期実行 → Laravel Scheduler
監視 → Horizon / Supervisor

CSVインポート、メール送信、画像変換、外部API連携などは、ジョブ化してキューで処理すると安定しやすくなります。

大量処理があるアプリの場合

大量データを扱うアプリでは、キュー基盤やワーカーの管理が重要になります。

キュー基盤 → Redis / SQS
ワーカー → 複数台
進捗管理 → DB
ログ → 集約ログ
失敗処理 → リトライ + dead letter queue

大量処理では、処理速度だけでなく、途中で失敗したときに再開できることが重要です。

高負荷・リアルタイム性が必要なアプリの場合

高負荷なAPIサーバーやリアルタイム通信が必要な場合は、次のような構成を検討します。

Swoole / OpenSwoole
ReactPHP
専用WebSocketサーバー
Redis Pub/Sub
メッセージキュー
複数ワーカー

ただし、SwooleやOpenSwooleは通常のPHP-FPMとは実行モデルが異なるため、導入前に設計や運用方針を十分に検討する必要があります。

PHPの並列処理でよくある失敗例

PHPの並列処理では、実装方法そのものよりも、設計の失敗で問題が起きることが多いです。

Webリクエスト中に重い処理をすべて実行する

よくある失敗が、Webリクエスト中に重い処理をすべて実行してしまうケースです。

<?php

public function upload(Request $request)
{
    // CSVを全部処理
    // 画像も全部変換
    // メールも全部送信
    // API連携も全部実行

    return 'done';
}

このような処理は、タイムアウトやメモリ不足の原因になります。

改善するなら、次のようにキューへ登録します。

<?php

public function upload(Request $request)
{
    ProcessUploadedFile::dispatch($path);

    return '処理を開始しました';
}

Webリクエストでは「処理を受け付ける」だけにして、実際の重い処理はワーカーに任せる方が安全です。

並列数を無制限にする

大量の処理をすべて同時に実行しようとすると、サーバーや外部APIに大きな負荷がかかります。

1000件のURLへ一気にアクセスする
100個のワーカーを同時に起動する
大量のDB更新を同時に実行する

このような設計は、エラーやパフォーマンス低下の原因になります。

改善するには、同時実行数を制限します。

同時実行数を5〜10程度にする
APIごとのレート制限を守る
DB接続数を監視する
負荷を見ながらワーカー数を調整する

結果集約を考えていない

並列処理では、複数の処理が別々に実行されます。

そのため、処理結果をどう集約するかを事前に決めておく必要があります。

どの処理が成功したか
どの処理が失敗したか
どこまで完了したか
再実行できるか
重複実行しても問題ないか

結果集約を考えずに並列処理を導入すると、失敗時の復旧が難しくなります。

PHPの並列処理を学ぶおすすめの順番

PHPの並列処理を学ぶなら、いきなり高度な拡張やフレームワークを使うより、段階的に理解するのがおすすめです。

まずは通常の逐次処理を正しく書く

最初に大切なのは、通常の処理を安全に書くことです。

<?php

foreach ($items as $item) {
    process($item);
}

この段階で、次の点を整理しておきます。

エラー処理
ログ
タイムアウト
メモリ使用量
処理件数
失敗時の再実行

逐次処理が不安定なまま並列化すると、問題がさらに複雑になります。

HTTP通信ならcurl_multi_execを試す

外部APIを複数呼び出す処理なら、curl_multi_exec を試すと並行処理の考え方を理解しやすいです。

ただし、実務ではHTTPステータス、エラー、タイムアウト、レート制限を必ず確認します。

Webアプリならキューを学ぶ

LaravelなどのWebアプリでは、キューを学ぶのが実務的です。

重い処理をジョブ化し、ワーカーで処理する設計は、多くのWebアプリで使われます。

php artisan queue:work

複数ワーカーを起動すれば、複数ジョブを並列に処理できます。

CLIバッチならpcntl_forkを学ぶ

CLIバッチで本格的に複数プロセスを扱いたい場合は、pcntl_fork を学ぶとよいでしょう。

ただし、DB接続、プロセス管理、ゾンビプロセス、ログ、結果集約などを自分で設計する必要があります。

必要になったらparallelやReactPHPを検討する

parallel やReactPHPは強力ですが、導入や設計の難易度はやや高めです。

最初から使うのではなく、要件が明確になってから検討する方が安全です。

まとめ

PHPで並列処理を行う方法は複数あります。

ただし、PHPで「並列処理」と呼ばれるものには、厳密な並列処理だけでなく、並行処理、非同期処理、バックグラウンド処理も含まれます。

目的に応じて、次のように使い分けるとよいでしょう。

やりたいことおすすめの方法
外部APIを複数同時に呼ぶcurl_multi_exec、Guzzle Pool
Webアプリの重い処理を裏側で実行するキュー、複数ワーカー
CLIで複数プロセスに分けたいpcntl_fork
CPU処理を本当に並列化したいparallel、複数プロセス
非同期I/Oを本格的に扱いたいReactPHP、Amp、Swoole
リアルタイム通信を行いたいSwoole、OpenSwoole、WebSocket系
定期処理を行いたいcron、Laravel Scheduler、CLIバッチ

実務では、いきなり pcntl_forkparallel を使うより、まずは キュー・ジョブ・ワーカー を中心に設計するのがおすすめです。

特にWebアプリでは、ユーザーのリクエスト中に重い処理を完了させようとせず、処理をキューに登録し、裏側のワーカーで実行する構成が安定します。

PHPの並列処理で大切なのは、単に「同時に動かす」ことではありません。

どこを並列化するべきか
どこは順番を守るべきか
失敗時にどう復旧するか
重複実行しても問題ないか
外部APIやDBに負荷をかけすぎないか

これらを考えたうえで、適切な方法を選ぶことが重要です。

以上、PHPの並列処理についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次