TT Lab
はじめる
学ぶ 学習パス コース

Tomcat & nginxの運用

502にタイムアウトを延ばす人にならない

TT Labで続きを見る

一言でいうと

502は「無効なレスポンスを受け取った」で、504は「時間内に応答がなかった」であり、この2つを混同すると、タイムアウトを延ばすだけで何時間も費やします。

なぜこれが問題なのか

障害対応で最も時間を食うのは、原因を見つけられないことではなく、間違った原因を確信することです。502を「バックエンドが死んだ」と決めつけると、バックエンドを再起動し、直らなければタイムアウトを延ばし、それでも直らなければサーバーを増やします。その間、本当の原因であるレスポンスヘッダーのサイズやコネクションの早期終了には、手を付けません。

数字があれば、この確信が根拠に変わります。アクセスログからURLごとの応答時間の上位と5xxの分布を取り出し、nginxのエラーログでconnection refusedとupstream timed outを別々に数えれば、502と504が自然に分かれます。この2つの数字を数えるのにかかる時間は、1分です。

502と504は別の話である

コード 意味 nginxがこれを出すとき よくある原因
502 Bad Gateway アップストリームから無効なレスポンスを受け取った場合、または接続が拒否された場合 接続拒否、レスポンスのパース失敗、コネクションの早期終了 バックエンドのダウン、バックログの飽和、レスポンスヘッダーがバッファーより大きい
504 Gateway Timeout アップストリームが決められた時間内に応答しなかった場合 proxy_read_timeoutの超過 遅いクエリ、外部連携のレイテンシ、ロック待ち
503 Service Unavailable 利用可能なアップストリームがない場合 すべてのサーバーが失敗として除外された場合 全体障害、ヘルスの判定エラー

ここで最もよく間違えるのは、これです。502を「アップストリームが死んだ」と決めつけること。

仕様が言う502は、「ゲートウェイが上流から無効なレスポンスを受け取った」であって、「上流が応答しなかった」ではありません。応答は来たのに、プロキシが処理できなかった場合も、502です。代表的なのが、レスポンスヘッダーがプロキシバッファーより大きい場合です。

この状況が厄介な理由: アップストリームのヘルスチェックは、ずっと正常と出ます。ヘルスチェックの応答はヘッダーが小さいからです。そのため、「サーバーは問題なく動いているのに、特定のユーザーだけ502」になります。その特定のユーザーは、たいていSSOでログインしてCookieが大きいユーザーです。

proxy_buffer_size       16k;
proxy_buffers         4 32k;
proxy_busy_buffers_size 64k;

そして必ず覚えておくこと: 502が出ているのにタイムアウトを延ばしても、何の効果もありません。タイムアウトは504の話です。この2つを混同すると、何時間も無駄にします。

診断のはしご: curlの5段階

症状があいまいなときは、この順序で絞り込みます。特に5段階目が、探索空間を半分にします。

# 1. 이름이 풀리는가
getent hosts api.example.com

# 2. 포트가 열려 있는가 (ping 은 이 환경에서 안 되니 쓰지 않는다)
curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 3 http://api.example.com/

# 3. 상태코드와 헤더
curl -sSI https://api.example.com/health

# 4. 시간 분해
curl -s -o /dev/null -w 'dns=%{time_namelookup} conn=%{time_connect} \
tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://api.example.com/

# 5. ★ 프록시를 건너뛰고 업스트림에 직접, 원래 Host 헤더를 유지한 채
curl -sSI -H 'Host: api.example.com' http://127.0.0.1:8080/health

このコードブロックの韓国語コメントは、順に、名前が解決できるか、ポートが開いているか(pingはこの環境では使えないので使わない)、ステータスコードとヘッダー、時間の分解、プロキシを飛ばしてアップストリームに直接(元のHostヘッダーを維持したまま)、という意味です。

5段階目の結果の解釈が核心です。

-H 'Host: ...'を外すと、この比較は無意味です。バーチャルホストのルーティングがかかっている場合、Hostが違うとまったく別のアプリに行くからです。

4段階目の時間の分解も有用です。

GCログを読む最低限

[2026-08-19T02:14:33.221+0900][12.334s] GC(41) Pause Full (System.gc()) 486M->402M(512M) 812.443ms

この1行から読み取ること。

判断の基準は、絶対値ではなく傾向とパターンです。

OOMが起きたとき

java.lang.OutOfMemoryErrorは、種類によって対応が完全に異なります。

メッセージ 意味 最初の対応
Java heap space ヒープ不足またはリーク ヒープダンプの分析。やみくもに-Xmxを上げても、リークはそのままです
Metaspace クラスメタデータの不足 繰り返しのデプロイによるクラスローダーリークを疑います。再起動後に観察します
GC overhead limit exceeded GCに時間の大半を使うのに、ほとんど回収できない状態 事実上リークです。ヒープダンプ
unable to create native thread スレッド生成の失敗 ヒープではなく、OSの制限/スレッドリーク。スレッドダンプ

最後の行が特に紛らわしいです。OOMなのにヒープの問題ではない場合です。-Xmxを上げると、かえって悪化することがあります(ヒープが大きくなると、スレッド用のメモリが減ります)。

そしてダンプは、発生した瞬間にしか取れません。-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<경로>(プレースホルダーはパスです)を事前に入れておかないと、「再現したらそのときに見ましょう」になり、再現はたいてい起きません。

再起動の前に、証拠を残す

障害対応の原則は、復旧が優先です。ただし、再起動ボタンを押す前の1–2分で、この3つは確保できます。

PID=$(pgrep -f tomcat | head -1)
jcmd $PID Thread.print            > /tmp/threads_$(date +%H%M%S).txt
jcmd $PID GC.heap_info            > /tmp/heap_$(date +%H%M%S).txt
cp /opt/tomcat/logs/catalina.out    /tmp/catalina_$(date +%H%M%S).out

この1–2分を惜しむと、原因を永遠に見つけられません。「とりあえず再起動したら直りました」が3回繰り返されると、4回目には再起動でも直らず、そのときには証拠が何もありません。

ログから実際に抜き出すべきもの

アクセスログから、すぐに抜き出すべき4つです。

  1. ステータスコードの分布: 5xxがいつから増えたか
  2. 応答時間の上位URL: どこが遅いか
  3. アップストリームごとのエラー分布: 特定のサーバーだけが問題か
  4. 1分あたりのリクエスト数: トラフィックの急増が原因か、結果か

4つ目が重要です。障害のときにリクエストが増えたのが原因の場合もありますが、ユーザーが動かないのでリロードを繰り返して増えた結果であることも多いです。開始時刻を正確に見れば、区別できます。

現場での姿

最も厄介な形は、「サーバーは問題なく動いているのに、特定のユーザーだけ502」という形です。ヘルスチェックの応答はヘッダーが小さいため常に通り、セッションCookieが大きい一部のユーザーだけが、プロキシバッファーを超えて502を受け取ります。このときモニタリングのダッシュボードはすべて緑なので、ユーザーからの問い合わせが唯一のシグナルです。

そして、再起動の前に証拠を残す習慣が必要です。ヒープダンプとスレッドダンプは、再起動した瞬間に消え、同じ障害がまた起きるまで、原因がわからなくなります。