ぽっぺんしゃんにょろりんこ

匿名・非追跡型アダルト動画検索エンジンの設計ノート

PHPのproxy.phpがFPMを詰まらせていた ─ サムネイル画像のcurlがタイムアウトせず重くなる話

こんにちは、にょろりんこの備忘録的技術ブログです。

今日は、運営中の匿名検索エンジン「Sae-Porns」が「なんか変…」という現象に悩まされていました。

ページがやたらと重い。サムネイルがいつまで経っても表示されない。最悪、白画面のまま固まることも…

nginx や php-fpm を確認しても一見正常。でもログを見てみたら、proxy.php というファイルにリクエストが殺到していて、upstream timed out が山のように出ていました。

どうやら、この proxy.php が外部の画像URLを取りにいって詰まりまくっていたのが、すべての元凶だったようです。

修正後のコードはこちら。※修正前のコードはコピペで使われる方のことを考えてあえて載せていません

<?php
// proxy.php
// タイムアウト対策(FPM枯渇防止)
set_time_limit(10); // 最大10秒
ini_set('default_socket_timeout', 5); // ソケット5秒

// デバッグ用ログ出力
ini_set('display_errors', 1);
error_reporting(E_ALL);

// キャッシュ無効化
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("Expires: 0");

// 画像URL確認
if (!isset($_GET['image'])) {
    header("HTTP/1.1 400 Bad Request");
    echo "Error: No image parameter provided.";
    exit;
}

$imageUrl = $_GET['image'];

// cURL実行
$ch = curl_init($imageUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0");
curl_setopt($ch, CURLOPT_REFERER, "https://img.****.net/");
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // ← ここを追加(超重要)
$headers = [
    "Accept-Language: en-US,en;q=0.9",
    "Accept-Encoding: identity",
    "Connection: keep-alive"
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$imageData = curl_exec($ch);

if (curl_errno($ch)) {
    error_log('CURL Error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);

// ログ確認用
error_log("proxy.php: HTTP Code: $httpCode, Content-Type: $contentType");

if ($httpCode !== 200 || !$imageData) {
    header("Content-Type: image/png");
    readfile("noimage.png");
    exit;
}

if (ob_get_level()) {
    ob_end_clean();
}

if (!$contentType) $contentType = "image/jpeg";

header("Content-Type: $contentType");
header("Content-Length: " . strlen($imageData));

echo $imageData;
exit;
?>

この proxy.php は、いわゆるHTTPプロキシではなく、外部の画像をサーバー側で取得して返す「画像中継スクリプト」です。

<img src="/proxy.php?image=https://example.com/image.jpg">

という形で呼び出され、裏側でPHPcurl_exec() を使って外部画像を取得して返しています。HTTPS化や外部参照の保護のためによくある設計なのですが、外部画像サーバが遅いと、PHP-FPMが詰まってサイト全体が応答しなくなるという落とし穴がありました。

なぜこれが問題だったのか?

PHP-FPM(FastCGI Process Manager)は、同時に処理できるPHPリクエスト数が設定で決まっています。私の環境では、pm.max_children = 5 というデフォルト設定のままでした。

つまり、最大5件のPHP処理が同時に走ると、それ以上のリクエストは待たされるか、タイムアウトしてしまうということです。

ティアラとコーヒー

proxy.php が外部画像の読み込みで数十秒もフリーズすると、それだけでFPMのプロセスが塞がれ、他の重要なPHPスクリプト(ログイン、検索、ページ表示など)も巻き添えで止まってしまいます。

するとどうなるか、はい、ログイン処理も、検索処理も、ページの表示も、全部巻き添えで止まります。

どれか1件が遅いだけで、他のユーザーのリクエストまで詰まってしまうという、かなり危険な状態でした。

それで、ここです

curl_setopt($ch, CURLOPT_TIMEOUT, 5); // ← ここを追加(超重要)

このたった1行で、「遅い外部サーバには5秒だけ付き合って、それ以上は見切って進む」という動きになります。PHP-FPMが詰まらなくなって、サイト全体がスムーズに動くようになりました。

備忘メモ

  • file_get_contents() や curl_exec() などで外部URLを扱うときは、
  • set_time_limit()(スクリプト全体の実行時間)
  • CURLOPT_TIMEOUT(curlの待ち時間)
  • default_socket_timeout(PHPソケットの待ち時間)

この3点セットは 最初から仕込んでおくべきセーフティネットだなと、今回あらためて痛感しました。

作成中の貴方を追跡しないアダルト動画のサーチエンジン「Sae-Porns」はこちら。

それではみなさん、よき開発ライフを。

sae-porns.org