【無料ツール】画像をPDFに変換するPhotoPDF Appを公開しました!

【Nginx応用講座 第1回】大規模負荷分散とキャッシュ戦略。数万リクエストをさばく「Proxy Cache」と「Upstream」の極意

記事内に広告が含まれています。
[PR]

「動く」のその先へ。「止まらない・待たせない」インフラを作る。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
これまでの「Nginx基本講座(全8回)」では、インストールからHTTPS化、基本的なパフォーマンスチューニングまでを学びました。
これだけでも中小規模のWebサイトなら十分に運用可能です。

しかし、あなたのサービスが急成長し、秒間アクセス数が100、1,000、10,000と増えていった時、今の設定のままで耐えられるでしょうか?
バックエンドのデータベースが悲鳴を上げ、APサーバーがメモリ不足で落ちる……そんな悪夢を未然に防ぐのが、今回から始まる「応用講座」のテーマです。

コウ君

先生、お久しぶりです!
基本講座のおかげでWebサーバーは立てられるようになったんですが、最近ちょっと悩みがあって。
バックエンドのNode.jsアプリを3台に増やしたんですけど、なんか負荷が偏ってる気がするんです。
あと、会員サイトなので「キャッシュ」の設定が怖くて……。間違って他人のマイページが見えちゃったらどうしようって。

リナックス先生

コウ君、いい着眼点ね。
「とりあえずラウンドロビンで振り分ける」「なんとなくキャッシュする」のはアマチュアの仕事。
プロは「リクエストの性質に合わせて振り分けアルゴリズムを選ぶ」し、「キャッシュすべきものとすべきでないものを厳密に制御」するの。
応用講座では、Nginxを単なるWebサーバーとしてではなく、最強の「アプリケーション・デリバリー・コントローラー」として使い倒すわよ!

本記事では、Nginx Open Source版で実現可能な高度なロードバランシング手法、バックエンドの負荷を劇的に下げるコネクションプーリング、そして動的サイトを静的サイト並みに高速化する「マイクロキャッシュ」戦略について解説します。

🚀 Nginx応用講座(全8回)カリキュラム

現在地:【第1回】大規模負荷分散とキャッシュ戦略。数万リクエストをさばく「Proxy Cache」と「Upstream」の極意

  • 【第1回】大規模負荷分散とキャッシュ戦略。数万リクエストをさばく「Proxy Cache」と「Upstream」の極意
  • 【第2回】WAF(Web Application Firewall)の構築。ModSecurityと最新のNginxセキュリティ
  • 【第3回】マイクロサービス時代のルーティング。gRPC、WebSocket、認証プロキシ(auth_request)の統合
  • 【第4回】Nginxをプログラマブルに。Lua言語(OpenResty)による動的処理と機能拡張
  • 【第5回】絶対停止させない高可用性(HA)。KeepalivedによるVIP管理とフェイルオーバー
  • 【第6回】可観測性(Observability)の確保。Prometheus/Grafana連携とカスタムメトリクス
  • 【第7回】動的モジュールとカスタムビルド。必要な機能だけを組み込む軽量化テクニック
  • 【第8回】Kubernetes Ingress Controller入門。クラウドネイティブ時代のNginx運用

※基本講座の復習はこちら:Nginx基本講座(全8回)アーカイブ


第1章:ラウンドロビンを超えて。高度な負荷分散アルゴリズム

基本講座では、順番にリクエストを振り分ける「ラウンドロビン」を紹介しました。
しかし、現実のアプリケーションでは、処理によって重さが違います。「軽い処理」ばかり担当するサーバーと、「重い集計処理」に当たってしまったサーバー。
これらを単純に順番で割り振ると、重い処理を抱えたサーバーがパンクしてしまいます。

1. Least Connections(最小接続数)

「今、一番手が空いているサーバー」に仕事を回す方式です。
処理時間がバラバラなリクエストが混在する環境(動画エンコード、レポート生成など)で威力を発揮します。

upstream backend_servers {
    least_conn;  # ← これを追加するだけ
    server 192.168.1.101:3000;
    server 192.168.1.102:3000;
    server 192.168.1.103:3000;
}

2. IP Hash / Generic Hash(スティッキーセッション)

「ログイン状態(セッション)」をAPサーバーのローカルメモリに保存しているようなレガシーなアプリでは、ユーザーが常に同じサーバーにアクセスする必要があります。
これを実現するのがハッシュ方式です。

upstream backend_servers {
    ip_hash;  # クライアントIPに基づいて固定
    server 192.168.1.101:3000;
    server 192.168.1.102:3000;
}

注意点: ip_hash は、NAT越しのアクセス(会社のゲートウェイ経由など)で偏りが発生しやすいです。
Nginx 1.7.2以降では、より柔軟な hash ディレクティブが使えます。

# URL(URI)に基づいて固定する例(キャッシュヒット率向上に有効)
upstream backend_servers {
    hash $request_uri consistent;
    server 192.168.1.101:3000;
    server 192.168.1.102:3000;
}

3. Random with Two Choices(2択ランダム)

最近のトレンドです。
「ランダムに2つのサーバーを選び、そのうち接続数が少ない方」を選びます。
Least Connectionsよりも計算コストが低く、かつラウンドロビンより偏りが少ないため、マイクロサービスのような大量の小規模バックエンドがある環境で推奨されます。

upstream backend_servers {
    random two least_conn;
    server 192.168.1.101:3000;
    server 192.168.1.102:3000;
    server 192.168.1.103:3000;
}

第2章:バックエンドを守る「コネクションプーリング」

Nginxはデフォルトでは、クライアントからのリクエストごとに、バックエンドサーバーへ新しいTCP接続(3ウェイ・ハンドシェイク)を行い、処理が終わると切断します。
これは非常に丁寧ですが、高負荷時には「接続・切断」のオーバーヘッドが無視できなくなります。

Keepaliveの設定(Upstream Keepalive)

Nginxとバックエンドの間で接続を維持(使い回し)することで、CPU負荷とレイテンシを劇的に下げます。

upstream backend_servers {
    server 192.168.1.101:3000;
    server 192.168.1.102:3000;
    
    # アイドル状態で維持する接続数
    keepalive 64;
}

server {
    location /api/ {
        proxy_pass http://backend_servers;
        
        # 【重要】HTTP/1.1を使う(1.0はKeepaliveできない)
        proxy_http_version 1.1;
        
        # 【重要】Connectionヘッダーを空にする
        proxy_set_header Connection "";
    }
}

プロの鉄則:
この設定は、特にSSL終端をNginxで行い、バックエンドへはHTTPで流す構成の場合に効果絶大です。
バックエンド側のTIME_WAIT枯渇問題も解決できます。


第3章:コンテンツキャッシュの極意「Proxy Cache」

Webアプリのパフォーマンス改善で最も効果があるのは、「処理させないこと」です。
APサーバーが生成したHTMLやJSONをNginxが一時保存(キャッシュ)し、2回目以降はNginxが即答する。
これにより、APサーバーの負荷はゼロになります。

ステップ1:キャッシュ領域の定義(httpブロック)

まず、キャッシュを保存する場所とルールを定義します。

http {
    # path: 保存場所
    # levels: ディレクトリ階層(パフォーマンスのため分散させる)
    # keys_zone: 共有メモリの名前とサイズ(管理領域)
    # max_size: キャッシュの最大容量
    # inactive: アクセスがないキャッシュを削除するまでの時間
    proxy_cache_path /var/cache/nginx/my_cache levels=1:2 keys_zone=my_zone:10m max_size=1g inactive=60m;
}

ステップ2:キャッシュの有効化(locationブロック)

実際にキャッシュを使いたい場所で有効化します。

location / {
    proxy_cache my_zone;
    
    # ステータスコードごとのキャッシュ時間
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404      1m;
    
    # キャッシュキーの定義(デフォルトでも良いが、User-Agentなどを区別したい場合に調整)
    # proxy_cache_key "$scheme$request_method$host$request_uri";
    
    # バックエンドがダウンしていても、古いキャッシュ(Stale)を返して稼働し続ける
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    
    # キャッシュの状態をヘッダーで確認できるようにする(デバッグ用)
    add_header X-Cache-Status $upstream_cache_status;
    
    proxy_pass http://backend_servers;
}

💡 プロのノウハウ:proxy_cache_use_stale
この設定は「お守り」として最強です。
もしバックエンドのアプリが全滅してエラー(502 Bad Gateway)を返してきても、Nginxは手元にある「少し古いキャッシュ」をユーザーに返し続けます。
ユーザーから見れば「サイトは動いている」状態を維持できるため、障害対応の時間を稼ぐことができます。


第4章:事故を防ぐ「キャッシュ除外設定」

冒頭でコウ君が心配していた「他人のマイページが見えてしまう事故」。
これは、Cookie(セッションID)を持っているユーザーに対してまでキャッシュを返してしまうことで発生します。
これを防ぐには proxy_no_cacheproxy_cache_bypass を使います。

ログインユーザーはキャッシュしない設定

location / {
    # 以下の条件のいずれかが真の場合、キャッシュを使用しない(バイパスする)
    proxy_cache_bypass $http_cookie $arg_nocache;
    
    # 以下の条件のいずれかが真の場合、レスポンスをキャッシュ保存しない
    proxy_no_cache $http_cookie $arg_nocache;
    
    proxy_cache my_zone;
    proxy_pass http://backend_servers;
}

$http_cookie 変数は、リクエストにCookieが含まれている場合に値が入ります。
Nginxは「値が空でない、かつ0でない」場合を「真」と判定するため、Cookieを持っているユーザー(ログイン済み)のリクエストはキャッシュされず、常にバックエンドへ問い合わせるようになります。

※より厳密には、特定のCookie名(PHPSESSID_sessionなど)を正規表現でチェックして変数をセットし、それを判定に使うのがベストです。


第5章:動的サイトを爆速化する「マイクロキャッシュ」

ニュースサイトやブログなど、頻繁に更新されるが、全ユーザーに同じ内容を見せるサイト。
これらを「1秒だけキャッシュする」戦略を「マイクロキャッシュ」と呼びます。

なぜ1秒なのか?

秒間1,000リクエストが来るサイトがあったとします。
キャッシュなしでは、DBに対して毎秒1,000回のクエリが飛びます。
しかし、「有効期限1秒」のキャッシュを入れるとどうなるでしょう?

  1. 最初のリクエスト → バックエンドで生成(0.1秒かかる) → Nginxがキャッシュ保存。
  2. その後の0.9秒間に来た999リクエスト → Nginxがキャッシュを即答(DB負荷ゼロ)。
  3. 1秒後 → キャッシュ切れ。次のリクエストだけバックエンドへ。

つまり、毎秒1回しかバックエンドが動きません。
ユーザーから見れば「最大で1秒前の古い記事」が見えるだけです。
ニュースサイトにおいて1秒の遅れは誤差です。
しかし、サーバー負荷は1/1000になります。

マイクロキャッシュの実装例

location /news/ {
    proxy_cache my_zone;
    proxy_cache_valid 200 1s;  # 1秒だけキャッシュ!
    
    # 複数人が同時にキャッシュを作りにいかないようにロックする(Cache Stampede対策)
    proxy_cache_lock on;
    proxy_cache_lock_timeout 5s;
    
    proxy_pass http://backend_servers;
}

proxy_cache_lock on; も重要です。
キャッシュが切れた瞬間に100人が殺到しても、最初の1人だけをバックエンドに通し、残りの99人はその生成を待たせる(または古いキャッシュを返す)ことで、バックエンドへの集中砲火を防ぎます。


第6章:キャッシュの削除(Purge)について

「記事を修正したから、今すぐキャッシュを消したい!」
Nginx Open Source版(無料版)の最大の弱点がここです。
商用のNginx Plusには便利なAPIがありますが、無料版には標準でPurge機能がありません。

対策:proxy_cache_purge モジュールの利用

自分でNginxをビルドできる場合は、ngx_cache_purge モジュールを組み込むことで、特定のURLを叩いてキャッシュを消せるようになります。

しかし、現代の運用ではもっと単純な方法が取られます。
「キャッシュ時間を短くする(マイクロキャッシュ)」か、「URL自体を変える(Versioning)」か、あるいは「ディレクトリごと rm コマンドで消す」です。

rm -rf /var/cache/nginx/my_cache/*

乱暴に見えますが、確実に全キャッシュをクリアするならこれが最速です(※高負荷時には注意)。


まとめ:Nginxは最強の盾になる

お疲れ様でした!
今回は、大規模運用に不可欠なロードバランシングとキャッシュ戦略について解説しました。

今回の重要ポイント:

  • least_connrandom を使い分け、負荷の偏りを防ぐ。
  • keepalive でバックエンドとの接続を維持し、CPU負荷を下げる。
  • proxy_cacheuse_stale と組み合わせて可用性を高める。
  • ログインユーザーには proxy_no_cache でキャッシュを見せない。
  • 「マイクロキャッシュ」を使えば、動的サイトでも静的サイト並みに速くなる。

これらを駆使すれば、あなたのサーバーは数万アクセスが来ても涼しい顔をして動き続けるでしょう。

しかし、アクセスが増えれば増えるほど、次に怖くなるのが「悪意ある攻撃」です。
SQLインジェクション、XSS、脆弱性を突いたスキャン……。
これらをNginxレベルで防ぐための専用ファイアウォール「WAF」が存在します。

次回、第2回は「WAF(Web Application Firewall)の構築。ModSecurityと最新のNginxセキュリティ」です。
オープンソースWAFのデファクトスタンダードであるModSecurityをNginxに組み込み、アプリケーションを守るための鉄壁の要塞を構築します。
コンパイルの手順からルールの設定まで、ディープな世界へご案内します。お楽しみに!

▼ 大規模インフラ技術を実践する ▼

負荷分散環境をテストする
「VPS」で自分専用環境

おすすめVPSを見る

SRE・インフラエンジニアへ
「ITエンジニア転職」

転職エージェントを見る

[PR]

💡 サーバー構築やコマンドの練習には、VPSが圧倒的におすすめです

手元のパソコンや大切なメイン環境で検証を行うと、設定ミスでシステムを壊してしまったり、不要なパッケージが溜まって動作が不安定になるリスクがあります。

もしあなたが実務レベルのスキルを最短で身につけたいのであれば、月額ワンコインから使えて、失敗しても数分で初期状態にリセットできるVPS(仮想専用サーバー)を利用するのが、プロも実践する最も確実で安全な学習方法です。

▼ プロも推奨するVPS環境はこちら ▼

Nignx講座
linux工房をフォローする

コメント