DELOGs
[自サーバでNext.jsアプリを動かす#3]Nginxのキャッシュゾーン設定

自サーバでNext.jsアプリを動かす#3
Nginxのキャッシュゾーン設定

キャッシュゾーンを利用してサーバ負荷を軽減しながらNext.jsで構築したWebアプリケーションを運用

初回公開日

最終更新日

「自サーバでNext.jsアプリを動かす」の第3回はNginxのキャッシュゾーンの設定を行います。 Next.jsはWebアプリケーションが構築しやすい反面、SSR(サーバーサイドレンダリング)やrevalidateなどを利用するISR(インクリメンタルスタティックリジェネレーション)など、動的・静的なコンテンツが混在する構成をとっており、以下のような課題もあります。
  • 同じページが頻繁に再生成されてサーバ負荷が高くなる
  • 海外からのボットアクセスがSSRエンドポイントを攻撃してくる
  • パフォーマンス改善が必要(TTFB短縮)
この課題解決として、Nginxのキャッシュゾーンを使うことで、再生成済みのレスポンスをNginxが保持して高速に返せるようになります。
example.comというサイトを例に設定作業を進めていきます。

1.キャッシュ用ディレクトリの作成

まずはキャッシュファイルを保存するディレクトリを作成します。
bash
1sudo mkdir -p /var/cache/nginx/example_com 2sudo chown -R nginx:nginx /var/cache/nginx
Nginxが使用するのでパーミッションをnginxにしておきます。

2.nginx.conf にproxy_cache_pathを設定

nginx.confにキャッシュバスとキャッシュキーの設定を追記します。
bash
1sudo vi /etc/nginx/nginx.conf
diff : nginx.conf
1http { 2 include /etc/nginx/mime.types; 3 default_type application/octet-stream; 4 5+ proxy_cache_path /var/cache/nginx/example_com levels=1:2 keys_zone=example_com_cache:50m inactive=1h use_temp_path=off; 6+ proxy_cache_key "$scheme$request_method$host$request_uri"; 7 8 log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 9 '$status $body_bytes_sent "$http_referer" ' 10 '"$http_user_agent" "$http_x_forwarded_for"';

proxy_cache_pathの各パラメータの意味

パラメータ意味・役割
/var/cache/nginx/example_comNginxがキャッシュファイルを保存する物理パス。ディレクトリは事前に作成して権限設定しておく必要あり
levels=1:2キャッシュファイルを分散して保存するディレクトリ構造の深さ。:で何階層構造にするかを決めている。数値部分は何文字のディレクトリ名にするかを指定していて、ディレクトリ名はキャッシュキーのハッシュの先頭から自動で拾う。例えばキャッシュキーのハッシュが a1b2c3d4e5f6... だと、/a/1b/ 以下にキャッシュファイルが保存されるようになる。これにより大量のファイルが1ディレクトリに偏らず、ファイルシステムのパフォーマンスが落ちない
keys_zone=example_com_cache:50m名前付きキャッシュゾーンを定義。50m はメモリ上に保持されるキー情報の容量(あくまでキー情報だけをメモリ上に保存してキャッシュデータそのものはディスク)。ゾーン名(ここでは example_com_cache)は proxy_cache で参照
inactive=1h1時間アクセスがなければ対象のキャッシュを削除。ただし、削除は即時ではなくガベージコレクション時に行われる
use_temp_path=off一時ファイルを /var/cache/nginx/... に直接書き込む。デフォルトでは /var/lib/nginx/tmp/ に書かれるが、同じパスにした方がI/O効率がよくなることが多い

proxy_cache_keyの変数の意味

この設定は、キャッシュの一意性を決定するキーをどう構成するかを決めています。
変数説明
$schemehttp か https。スキームが違えば別のキャッシュとして扱われる
$request_methodGET, POST, HEAD など。通常は GET だけキャッシュするが、違うメソッドは別キャッシュになる
$hostホスト名(例: example.com)。同じIPで複数ドメイン運用時に有効
$request_uriパスとクエリ文字列(例: /blog?page=2)。クエリ違いも別キャッシュとして扱う

3.各サイトのserverブロックでキャッシュを利用

次に、各サイトのNginx設定にキャッシュ利用の設定を追加していきます。
bash
1sudo vi /etc/nginx/conf.d/example.com.conf
sshのseverブロックのlocation /ディレクティブへ追記します。
diff : example.com.conf
1server { 2 listen 443 ssl default_server; 3 listen [::]:443 ssl default_server; 4 http2 on; 5 listen 443 quic reuseport; 6 listen [::]:443 quic reuseport; 7・・省略・・ 8location / { 9 proxy_pass http://localhost:3000; 10 proxy_http_version 1.1; 11 proxy_set_header Upgrade $http_upgrade; 12 proxy_set_header Connection 'upgrade'; 13 proxy_set_header Host $host; 14 proxy_set_header X-Real-IP $remote_addr; 15 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 proxy_set_header X-Forwarded-Proto $scheme; 17 proxy_cache_bypass $http_upgrade; 18 19# ここからキャッシュ設定 20+ proxy_cache example_com_cache; 21+ proxy_cache_valid 200 302 1h; 22+ proxy_cache_valid 404 1m; 23+ add_header X-Cache-Status $upstream_cache_status; 24+ add_header X-Cache-Key $scheme$request_method$host$request_uri; 25 26 }

severブロック内のキャッシュ設定の意味

パラメータ説明
proxy_cache* どのキャッシュゾーンを使うか指定
* proxy_cache_path で定義した keys_zone=delogs_jp_cache:50m に紐づくゾーンを使う
proxy_cache_valid* HTTPレスポンスコード 200 と 302 を受け取ったレスポンスは、1時間キャッシュ
* 404 Not Found も1分間キャッシュして、同じリクエストが短期間に繰り返されるのを防止
add_header X-Cache-Status* キャッシュされたかどうかをレスポンスヘッダに追加
* $upstream_cache_status の値は次のいずれかになる
HIT:キャッシュから返された
MISS:バックエンドに問い合わせた(キャッシュされてなかった)
BYPASS:キャッシュバイパス条件にマッチ(例: Cookieなど)
EXPIRED:キャッシュは存在したが有効期限切れ、再取得後更新
add_header X-Cache-Key* 実際に使われたキャッシュキー(文字列)をヘッダとして返す
* $scheme$request_method$host$request_uri は、どのURL+メソッドがキャッシュ対象になったかを表す(例:httpsGETexample.com/blog?page=2)
少しわかりづらいのが、nginx.confで設定したinactive=1hproxy_cache_validの違いです。
  • proxy_cache_valid と inactive の違いを再確認
設定項目意味
proxy_cache_valid 1hキャッシュの「有効期間」。1時間後に期限切れ(再取得される)新しいページを定期的に反映したいときに使う
inactive=1hアクセスがないと削除される「保管期限」。1時間アクセスされなければ削除される過去に生成されたけど誰も見ないページを掃除するため
inactiveは該当ページが誰にもアクセスされない時に削除されるまでの時間を指定している感じです。

4.各種設定の目安

設定方法やパラメータの内容は把握できても、実際どんな値にすればいいのか、私自身は結構悩みました。
そこで、ChatGPTに相談して目安を弾き出してもらいました。その内容を共有しておきます。

keys_zone=...:50m の箇所の目安について

このサイズは「メモリに保持するキャッシュキーのインデックス情報だけ」の容量なので、私は50MBにしましたが、現状の当サイトの規模を考えるとこれでも少し多めかなと思っています。目安は下記のようです。
keys_zone想定されるキャッシュ件数
10m ~ 20m数千~1万件程度
30m ~ 50m(今の設定)数万~10万件
100m 以上数十万以上
私の場合、inactive=1hとしたので、この1時間の間にどれくらいのアクセスがあるかによるのですが、サイトのページ数(パラメータごとに一意として)にもよるので運用しながら調整する必要はありそうです。

levels=1:2 の箇所の目安について

1ディレクトリ内に数万ファイルがあると、UNIX系ファイルシステムは遅くなるということで、負荷分散するための設定ですが、これを第二階層までいいのか、もう少し階層を増やした方がいいのか、悩みました。
ChatGPTによる目安は下記のようになりました。
規模推奨Levels
小〜中規模(数千〜数万件)1:2(今回の構成)
中〜大規模(数十万件〜)2:2 または 1:2:1
超大規模(100万件〜)1:2:2 や 2:2:2
規模のカッコ内はキャッシュ件数になります。inactive=1hとしているので1時間のユニークキャッシュの件数ということになります。
たとえば…
  • サイトが毎分100リクエストある
  • そのうち一意のURLが半分だと仮定(50件/分)
  • → 1時間で最大 50 × 60 = 3,000 件のキャッシュキー この場合、3000件のキャッシュファイルが1時間以内に生成され、存在する可能性がある。
→ levels=1:2 で最大4096ディレクトリ分散なので、ファイルシステム的に余裕があるという計算になります。 正直、当サイト的には毎分100リクエストというのもかなり多めですが、Webプロモーションをガンガンやるサイトですと小中規模でもあり得る数値ですので、この辺も運用しながら調整していく必要がありそうです。

5. 運用時に各種数値の適正値を見極めるために

各種数値を運用しながらチェックして修正するためにはログにキャッシュの使用状況を分かりやすく出力して、それをチェックする方法も確立しておく必要があります。

ログ出力内容へ追加

再度nginx.confを編集して、ログ出力に$upstream_cache_status$scheme$request_method$host$request_uriを追加します。
bash
1sudo vi /etc/nginx/nginx.conf
bash : nginx.conf
1log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 2 '$status $body_bytes_sent "$http_referer" ' 3 '"$http_user_agent" "$http_x_forwarded_for"' 4 'cache_status=$upstream_cache_status cache_key="$scheme$request_method$host$request_uri"';
これにより:
  • cache_status=HIT や MISS, BYPASS, EXPIRED などがログに記録される
  • cache_key="httpsGETdelogs.jp/blog?page=2" のように、実際に使われたキャッシュキーも記録される

ログの確認方法

あとはgrepでaccess.logから情報を抽出して確認するればOKです。
bash
1# ヒット件数集計 2sudo grep 'cache_status=HIT' /var/log/nginx/access.log | wc -l 3 4# 未キャッシュの確認 5sudo grep 'cache_status=MISS' /var/log/nginx/access.log | wc -l 6 7# 一意キー数の確認 8sudo grep 'cache_key=' /var/log/nginx/access.log | sort | uniq

6. 最後にnginxの再読み込み

bash
1sudo nginx -t && sudo systemctl reload nginx
この構成により、不要なキャッシュヒットや誤キャッシュを防ぎ、ページ単位・パラメータ単位で正確にキャッシュされるようになります。次回はセキュリティヘッダと静的アセットのキャッシュ期間の設定です。自サーバでNext.jsアプリを動かすところまで、あと一歩です。
次回はこちら...
前回の確認はこちら...
この記事の執筆・編集担当
DE

松本 孝太郎

DELOGs編集部/中年新米プログラマー

ここ数年はReact&MUIのフロントエンドエンジニアって感じでしたが、Next.jsを学んで少しずつできることが広がりつつあります。その実践記録をできるだけ共有していければと思っています。