MeWrite Docs

Fatal error: Maximum execution time exceeded

PHPスクリプトの実行時間が制限を超えた場合に発生するエラー

概要

Fatal error: Maximum execution time of X seconds exceeded は、PHPスクリプトが max_execution_time で設定された実行時間の制限を超えた場合に発生するエラーです。

エラーメッセージ

Fatal error: Maximum execution time of 30 seconds exceeded in /app/process.php on line 42
PHP Fatal error: Maximum execution time of 60 seconds exceeded

原因

  1. 重い処理: 大量のデータ処理や複雑な計算
  2. 外部API待ち: レスポンスが遅い外部サービス
  3. 無限ループ: 終了条件が満たされないループ
  4. N+1問題: データベースクエリが大量に発生
  5. max_execution_time が短い: デフォルト30秒では不足

解決策

1. max_execution_time を増やす(一時的な対処)

1
2
3
4
5
6
7
8
9
// スクリプト内で変更
set_time_limit(120);  // 120秒
set_time_limit(0);    // 無制限(非推奨)

// php.ini で変更
// max_execution_time = 120

// .htaccess で変更(Apache)
// php_value max_execution_time 120
1
2
# コマンドラインで変更
php -d max_execution_time=120 script.php

2. 処理を非同期化(キュー)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ❌ 重い処理を同期的に実行
public function processRequest()
{
    $this->sendEmails();     // 時間がかかる
    $this->generateReport(); // 時間がかかる
    return response()->json(['status' => 'ok']);
}

// ✅ キューに投入して非同期実行(Laravel)
public function processRequest()
{
    SendEmailsJob::dispatch();
    GenerateReportJob::dispatch();
    return response()->json(['status' => 'queued']);
}

3. 外部APIのタイムアウト設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ❌ タイムアウトなし
$response = file_get_contents('https://api.example.com/data');

// ✅ タイムアウトを設定
$context = stream_context_create([
    'http' => [
        'timeout' => 10  // 10秒でタイムアウト
    ]
]);
$response = file_get_contents('https://api.example.com/data', false, $context);

// ✅ cURL でタイムアウト設定
$ch = curl_init('https://api.example.com/data');
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);

// ✅ Guzzle でタイムアウト設定
$client = new \GuzzleHttp\Client([
    'timeout' => 10,
    'connect_timeout' => 5
]);
$response = $client->get('https://api.example.com/data');

4. データベースクエリの最適化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ❌ N+1 問題
$users = User::all();
foreach ($users as $user) {
    echo $user->profile->name;  // 毎回クエリが発行される
}

// ✅ Eager Loading
$users = User::with('profile')->get();
foreach ($users as $user) {
    echo $user->profile->name;  // キャッシュされている
}

// ✅ インデックスを確認
// 検索条件のカラムにインデックスがあるか確認
// EXPLAIN SELECT * FROM users WHERE email = '...'

5. バッチ処理に分割

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ❌ 一度に全処理
function processAllUsers()
{
    $users = User::all();
    foreach ($users as $user) {
        $this->heavyProcess($user);
    }
}

// ✅ バッチに分割
function processUsersInBatches($batchSize = 100)
{
    $processed = 0;

    User::chunk($batchSize, function ($users) use (&$processed) {
        foreach ($users as $user) {
            $this->heavyProcess($user);
            $processed++;
        }

        // 各バッチ後にタイムアウトをリセット
        set_time_limit(30);
    });

    return $processed;
}

6. CLI での実行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Webリクエストでの実行は制限がある
// CLI で実行すると max_execution_time は 0(無制限)

// artisan コマンドを作成(Laravel)
class ProcessUsersCommand extends Command
{
    protected $signature = 'users:process';

    public function handle()
    {
        // 時間のかかる処理
    }
}
1
2
3
4
5
# CLI で実行
php artisan users:process

# cron で定期実行
* * * * * cd /app && php artisan schedule:run >> /dev/null 2>&1

7. 進捗表示とチェックポイント

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function processLargeDataset()
{
    $items = getItems();
    $total = count($items);
    $checkpoint = 0;

    // 前回のチェックポイントから再開
    if (file_exists('checkpoint.txt')) {
        $checkpoint = (int) file_get_contents('checkpoint.txt');
    }

    for ($i = $checkpoint; $i < $total; $i++) {
        processItem($items[$i]);

        // 100件ごとにチェックポイント保存
        if ($i % 100 === 0) {
            file_put_contents('checkpoint.txt', $i);
            set_time_limit(30);  // タイムアウトリセット
        }
    }

    unlink('checkpoint.txt');
}

8. ignore_user_abort の設定

1
2
3
4
5
6
// ユーザーが接続を切断しても処理を続行
ignore_user_abort(true);
set_time_limit(0);

// 長時間の処理
processLongRunningTask();

FastCGI/FPM の設定

1
2
3
4
5
6
; php-fpm.conf または pool.d/www.conf
; リクエストのタイムアウト
request_terminate_timeout = 120s

; Nginx の設定も必要
; fastcgi_read_timeout 120;

デバッグのコツ

実行時間の計測

1
2
3
4
5
6
$start = microtime(true);

// 処理

$elapsed = microtime(true) - $start;
echo "Elapsed: {$elapsed} seconds\n";

ボトルネックの特定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Xdebug でプロファイリング
// php.ini: xdebug.mode = profile

// または手動で計測
$checkpoints = [];

$checkpoints['start'] = microtime(true);
step1();
$checkpoints['step1'] = microtime(true);
step2();
$checkpoints['step2'] = microtime(true);

// 各ステップの時間を計算
$prev = null;
foreach ($checkpoints as $name => $time) {
    if ($prev !== null) {
        $duration = $time - $prev;
        echo "$name: {$duration}s\n";
    }
    $prev = $time;
}

PHP の他のエラー

最終更新: 2025-12-08