
リクエストが詰まって返らない|ワーカー枯渇の見つけ方と一次対処
「サイトがまったく返ってきません」と連絡が入って、あわててサーバーに入る。ところが top を見ても CPU はスカスカ、メモリにも余裕がある。ディスクも埋まっていない。なのに、ブラウザで開くとぐるぐる回ったまま返ってこない——保守運用をしていると、この「リソースは余っているのに詰まっている」という状況に一度は出くわしますよね。
数字が正常だと、どこを見ればいいのか分からなくなります。でも、こういうときに疑いたいものがひとつあります。受け口(ワーカー・スレッド)の枯渇です。サーバーは「同時に何本まで処理を受けるか」という上限を持っていて、その席がすべて埋まると、あとから来たリクエストは待たされるだけになります。席が埋まっているだけなので、CPU もメモリも忙しくは見えません。
この記事では、この「受け口が埋まって詰まる」状態を、見つけ方 → 犯人の探し方 → 安全な一次対処の順で一緒に整理します。構成(Apache、nginx+php-fpm、アプリサーバー+DB など)で見る場所は変わりますが、考え方はそのまま使えます。
結論:リソースに余裕があるのにサイトが返らないときは、同時処理数の上限(受け口)が埋まっていないかをまず見ます。手順は、①ワーカーの使用数と待ち行列を見る(何席中、何席が埋まっているか) → ②埋めている処理の中身を見る(どの画面・どの処理が長く居座っているか) → ③一次対処は「上限を上げる」より先に「詰まりの元を止める」。上限を上げるのはメモリと引き換えの応急処置で、遅い処理をそのままに数字だけ増やすと、席が増えたぶんメモリを食い尽くして別の障害に化けることがあります。
何が起きているか:CPUが暇でも「席」は埋まる
Web サーバーやアプリサーバーは、リクエストを無制限に受けるわけではありません。「同時にここまで」という受け口の数が決まっています。呼び方は構成によって違いますが、役割は同じです。
- Apache(prefork/worker/event):
MaxRequestWorkersで決まる同時処理数。 - php-fpm:
pm.max_childrenで決まる、同時に動ける子プロセス数。 - アプリサーバー(Puma・Unicorn・Tomcat など):ワーカー数/スレッドプールの上限。
- DB(MySQL など):
max_connectionsで決まる同時接続数。
大事なのは、この席は「処理が終わるまで」返ってこないということです。だから、次の式のような関係になります。
- 1リクエストの処理に 0.1秒しかかからないなら、席が10個でも 1秒あたり100件さばけます。
- ところが1リクエストが 10秒かかるようになると、同じ10席で 1秒あたり1件しかさばけません。
ここが「CPU が暇なのに詰まる」の正体です。遅い処理の大半は、CPU を使って計算しているのではなく、何かを待っているからです。DB の応答待ち、外部 API の応答待ち、ロックの解放待ち——待っている間、席は占有されたままなのに、CPU はほとんど働いていません。だから top は静かなのに、受け口だけが満席になります。
そして満席になると、あとから来たリクエストは順番待ちの列に並びます。列が伸びるほど待ち時間は長くなり、やがて手前のプロキシがしびれを切らして 502・504 を返す——という形で表に出てきます。
つまり、詰まりの本当の原因は「席の数が足りない」ことではなく、席に長く座り続けている処理がいることのほうが多いのです。次から、その席の埋まり具合を確かめる順番を見ていきます。
詰まりを見つける順番:席 → 列 → 中身

順番はシンプルです。いま何席埋まっているか(席)→ どれくらい待たされているか(列)→ 誰が座っているか(中身)。この順で見ると、上限を触るべきか、処理を直すべきかが見分けられます。
① 何席中、何席が埋まっているかを見る
まずは「本当に満席なのか」を確かめます。ここが分からないまま設定をいじると、当てずっぽうになります。
- php-fpm:
pm.status_pathを有効にしておくと、active processes(使用中)・total processes(総数)・max active processes(これまでの最大)・listen queue(待ち行列)が確認できます。activeがmax_childrenに張り付いているなら満席です。設定していない場合でも、psで php-fpm の子プロセス数を数えれば、上限にいくつ近いかは分かります。 - Apache:
mod_statusのserver-statusで、処理中のワーカー数と空きスロットが見えます。空きが0に張り付いていれば満席です。 - nginx:
stub_statusでactive connectionsやwaitingの状態が見えます。nginx 自身は受け口が広いことが多いので、詰まりはその奥(php-fpm やアプリ)にあることが大半です。 - DB:MySQL なら
SHOW PROCESSLIST;で同時接続と、その状態(Sending data、Lockedなど)が見えます。接続数がmax_connectionsに近ければ、詰まりは DB 側かもしれません。
見るポイントは、上限に対して何割埋まっているかです。「たまに8割」なら余裕あり。「ずっと10割に張り付き」なら、すでに満席で列ができています。
② 待ち行列ができているかを見る
満席かどうかと同じくらい大事なのが、あとから来たリクエストが待たされているかです。
- php-fpm の
listen queue:0以外の数字が出ていれば、受けきれずに並んでいる状態です。ここが増え続けているなら、さばく速度より来る速度が上回っています。 - 手前のログの応答時間:nginx や Apache のアクセスログに応答時間(
$request_timeなど)を出していれば、時間が急に伸びた時刻が分かります。出していなければ、これを機に足しておくと次から一気に楽になります。 - 502・504 が出ているか:受け口が満席のまま返らず、手前のプロキシが時間切れで切ると 5xx になります。5xx とセットで出ているなら、詰まりの疑いは濃くなります。
ここでひと呼吸。数字が正常なのに障害が起きていると、「自分が見落としているだけでは」と不安になりますよね。でも、CPU もメモリも正常なのに詰まるというのはよくある型で、あなたの見落としではありません。見る場所が一つ違っただけです。
③ 席に座り続けている「中身」を見る
満席と分かったら、誰が長く居座っているかを見ます。ここが分かると、対処が「数字いじり」から「原因つぶし」に変わります。
- php-fpm:
pm.status_pathの full 表示(?full)で、各子プロセスがいまどのURIを処理していて、何ミリ秒経っているかが見えます。同じURLばかりが長時間並んでいれば、そこが犯人です。slowlogを設定しておくと、時間のかかったリクエストのスタックが残るので、あとからでも追えます。 - Apache:
server-statusの一覧に、処理中のリクエストのURLと経過時間が並びます。 - DB:
SHOW PROCESSLIST;で、Timeが大きいクエリやLocked状態の接続を探します。スロークエリログが有効なら、そちらのほうが確実です。 - 外部API:アプリのログに外部呼び出しの記録があれば、その所要時間を見ます。相手が遅いだけで席が埋まることは、実務でよくあります。
よくある居座りの正体は、だいたいこのあたりです。
- 重い集計・検索・CSV出力など、もともと時間のかかる画面。
- 遅い SQL(インデックスが効かなくなった、データが増えた)。
- 外部 API の応答待ち(相手が遅い・タイムアウト値が長すぎる)。
- ロック待ち(同じ行を更新し合って待ち続ける)。
- クローラーやリロード連打で、同じ重い画面が同時に何本も走っている。
具体例:「CPUは10%なのにサイトが返らない」を追う
よくある一件を、順番に切り分けてみます。
- 状況:問い合わせフォームからサイト全体が重いと連絡。
topを見ると CPU は10%前後、メモリも空きあり、ディスクも問題なし。それでもブラウザは返らず、ときどき504。 - ①席:php-fpm のステータスを見ると、
active processesがpm.max_children(この環境では10)に張り付いていた。=満席。 - ②列:
listen queueに数十件が並び、nginx のエラーログにもupstream timed outが出ていた。=列ができて、手前が時間切れで切っている。 - ③中身:ステータスの full 表示を見ると、10席のうち8席が同じ URL(月次集計のダウンロード画面)だった。経過時間はいずれも数十秒。DB 側の
SHOW PROCESSLIST;でも、同じ集計クエリが並んで走っていた。 - 分かったこと:月初で集計画面へのアクセスが重なり、1本あたり数十秒かかる処理が席を占有。残り2席で他の全ページをさばこうとして、サイト全体が詰まっていた。CPU が暇だったのは、大半が DB の応答待ちだったから。
- 一次対処:まず居座っている集計処理を止める(該当画面を一時的に閉じる/長時間走っているクエリを止める)。席が空いてサイト全体が戻ったのを確認してから、集計SQLの見直しと、集計画面を非同期(バックグラウンド処理)に寄せる改修を検討事項として起票した。
もしここで「とりあえず pm.max_children を10から50に増やす」だけをしていたら、一時的には返るようになったかもしれません。でも、1子プロセスあたりのメモリ × 50 がメモリを超えれば、今度はメモリ不足でプロセスが落ちる障害に化けます。席を増やす前に「誰が座っているか」を見られたから、次に効く直し方にたどり着けました。
影響:見る場所を1つ足すだけで、何が変わるか
「リソースは正常なのに返らない → 受け口を疑う」という一手を持っておくと、直す力そのものより先に、迷う時間が減ります。
- CPU・メモリが正常なのを見て手が止まる、という時間がなくなる。
- 「サーバーが壊れているのでは」と大ごとに構える前に、満席かどうかという具体的な問いに変えられる。
- 上限値をやみくもに上げて、メモリ不足という別の障害を呼び込むのを避けられる。
- 「重い画面が全体を巻き込む」構造が見えるので、改修の優先順位を説明しやすくなる。
逆に、毎回「上限を上げる」だけで乗り切っていると、席とメモリの綱引きが少しずつ苦しくなり、ある日まとめて効いてきます。見る順番は、その日の自分を助ける道具です。
明日やること:自分の現場の「席の数」を1枚に書く
障害のときに調べ始めると、どうしても遅くなります。明日できる、いちばん小さな一歩はこれです。
- 自分が担当するサーバーで、受け口の上限値を書き出す。php-fpm の
pm.max_children、Apache のMaxRequestWorkers、アプリサーバーのワーカー数、DB のmax_connections。分からないものは「未確認」と書くだけでも十分です。 - いまの使用数を1回だけ見て、横に書き添える(「10席中、平常時は2〜3席」など)。これが平常値になり、次に見たときの比較対象になります。
- ステータスの見方を1行メモする。php-fpm なら
pm.status_pathのURL、Apache ならserver-statusのURL、DB ならSHOW PROCESSLIST;。障害中に調べ直さなくていい状態にしておくのが目的です。 - まだ有効にしていなければ、php-fpm のステータスと slowlog(またはアプリの遅延ログ)を検証環境で試してから有効にする。ステータス画面はアクセス制限をかけてから公開設定にします。
- アクセスログに応答時間が出ていなければ、出す設定を検討する。次の詰まりのとき、時刻の特定が一気に楽になります。
きれいな資料でなくて大丈夫です。「何席あって、普段は何席使っていて、どこで見られるか」の3行があるだけで、次の自分(や引き継ぐ人)が、正常な数字の前で立ち尽くさずに済みます。
ワーカー枯渇チェックリスト
「リソースは正常なのに返らない」ときに確認する項目です。コピーして、自分のメモに当ててみてください。全部を毎回そろえる必要はありません。
まず外せない最低ラインはこの3つです。急いでいても、ここだけは押さえます。
- 【最低ライン】CPU・メモリ・ディスクに余裕があることを確かめたか(余裕があるなら受け口を疑う)
- 【最低ライン】ワーカー/同時接続が上限に張り付いていないかを見たか
- 【最低ライン】上限を上げる前に、席に長く座っている処理を1つでも特定したか
次の項目は、手前で片づかないとき・原因が奥にありそうなときに追加で確認します。当てはまらなければ飛ばして大丈夫です。
- 待ち行列(php-fpm の
listen queueなど)ができていないかを見たか - 同じURL・同じクエリばかりが並んで走っていないかを確かめたか
- 遅さの元が DB(スロークエリ・ロック待ち)か、外部APIの応答待ちかを切り分けたか
- クローラーやリロード連打など、外からの急な集中が重なっていないかを見たか
- 上限を上げる場合、1プロセスあたりのメモリ × 席数が搭載メモリに収まるか計算したか
- 設定を触る前に、いまの値と状態をメモして戻せるようにしたか
- 収まったあと、平常時の使用席数(平常値)を記録に残したか
全部に○が付かなくても大丈夫です。最低ラインの3つ、とくに「上限を上げる前に、誰が座っているかを見る」さえ押さえられれば、数字を大きくして様子を見るより、ずっと確かな一歩になります。
よければ、こちらも
受け口の詰まりは、5xx や「サイトが重い」と地続きです。近い切り分けを対にしておくと、次の一件がだいぶ軽くなります。
- 502・504エラーの切り分け|nginx・php-fpm・上流の順に見る:席が埋まって返らないとき、表に出るのはたいてい 5xx です。手前から奥へ切り分ける手順をまとめています。
- タイムアウトはどこで起きているか|多段構成の切り分け:どの段で時間切れになったかを一つに絞る考え方です。この記事と対にすると効きます。
- 「サイトが重い」の切り分け手順|DB・アプリ・ネットワークの順に見る:まだ返ってはいるが遅い、という段階での見る順番です。
- DBコネクション枯渇・デッドロックの見つけ方と一次対処:詰まりの元がDB側にあるときの、見つけ方から一次対処まで。

「リソースは全部正常なのに、サイトだけが返らない」——この状況がこわいのは、頼りにしている数字が何も教えてくれないからです。でも、サーバーには数字に出にくい「席の数」があって、そこが埋まっているだけ、ということがよくあります。席の埋まり具合を見る場所を1つ知っておくだけで、霧はかなり晴れます。
多くの場合、サーバー全体が壊れているのではなく、ただ一つの重い処理が、席を長く占有していただけです。今日は、自分の現場の「席の数」を3行メモしてみるところからで十分です。その3行が、次の「返ってきません」を、手探りではなく手順に変えてくれます。
ほかの実務ヒントは記事一覧からどうぞ。保守運用の小さな備えを、メールでも少しずつお届けしています。