0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

キャッシュは積むな、設計せよ!~WordPress×AWS高速化のレイヤ別設計論~

0
Posted at

はじめに

読了目安 25分

Redisを入れた。
CloudFrontも設定した。
OPcacheも有効にした。

それでも、なぜか遅い☹️

——その理由は単純です。

あなたは「キャッシュを積んでいる」だけで、設計をしていないのです

WordPress高速化はツールの問題ではありません。

  • どのレイヤで
  • どの負荷を
  • どう抑制するか

この3つを定量的に(=数値で)理解しているかどうかになります。

本記事では、AWS上でWordPressを本番運用する前提で、レイヤごとに必要な設計について解説していきます。
この記事がどなたかの参考になれば幸いです😼

対象読者

  • 「キャッシュを入れたのに効果が出ない」と感じているエンジニア
  • 設計根拠をもって説明できるようになりたいエンジニア
  • AWS上でWordPressを運用しているインフラエンジニア

前提環境

  • CloudFront → ALB → EC2(Nginx or Apache + PHP-FPM)
  • ElastiCache(Redis)
  • Aurora MySQL または RDS MySQL

1. 全体アーキテクチャ

レイヤ別責務の整理

各レイヤが 「何を」 キャッシュし、 「何を」 守るかを明確にしておくことが設計の出発点です。

レイヤ キャッシュ対象 守る対象 キャッシュにない(MISS)時の影響
CloudFront HTML・静的アセット(CSS/JS/画像) ALB / EC2 リクエストがそのままサーバーに届く
Page Cache(Nginx/Apache) WordPressが生成した動的HTML PHP-FPM PHPが毎回ページを生成する
OPcache コンパイル済みPHPバイトコード(PHPを機械語に変換したもの) CPU PHPファイルを毎回解釈し直す
Redis DBクエリ結果・セッション情報 Aurora / RDS 全クエリが毎回DBに届く

キャッシュは「速くするもの」ではなく、下位レイヤを守る盾です。
この意識がないと、全レイヤにキャッシュを入れても効果が薄い構成になってしまいます。


2. オリジン到達率

本記事で最も重要な式です。
すべての設計判断は、この オリジン到達率 で判断することになります。

P_origin = (1 - H_cf) × (1 - H_page)

つまり「CloudFrontでもPage Cacheでも捕まらず、PHPまで到達してしまうリクエストの割合」です。この値が小さいほど、サーバーの負荷は 低く なります。

  • H_cf:CloudFrontヒット率(CloudFrontが代わりに返せた割合)
  • H_page:ページキャッシュヒット率(Nginx/Apacheが保存済みHTMLを返せた割合)

なぜこの式が重要なのか

性能もコストも、この値が になるからです。

CloudFrontとPage Cacheはかけ算の関係です。
つまり、片方だけ高くても効果が 限定的 で、両方高めると効果が加速的に大きくなるという性質があります。

具体例)数値で見る影響度

総リクエスト 1,000 RPS(毎秒1,000回のアクセス)のサイトで、PHPが実行される回数がどう変わるか比較してみましょう。

シナリオ H_cf H_page P_origin PHPまで届く数 (RPS)
何もしない 0% 0% 100% 1,000
CloudFrontだけ 85% 0% 15% 150
Pageだけ 0% 90% 10% 100
両方 85% 90% 1.5% 15

「CloudFrontだけ」「Pageだけ」では100以上残るが、両方合わせるとたった15RPSになります。これが CloudFrontとPage Cacheのかけ算効果 です。


3. Aurora DBLoad統合モデル

セクション2のP_originが 「どれだけPHPまで到達するか」 を決めるとすれば、ここでは 「DBがどれだけ忙しくなるか」 を数値で測ります。

DBLoad とは、「DBが同時に処理しているクエリの重さ」を表す指標です。この値がDBのvCPU数(論理CPUコア数)を超えると、DBが処理しきれず応答が遅くなります。

DBLoad ≒ RPS_alb(CloudFront後のオリジン到達数)
        × (1 - PageCacheHit率)
        × Q_per_request
        × (1 - Redis削減率)
        × T_query

つまり「サーバーに届いたリクエスト数 × ページキャッシュで防げなかった割合 × 1ページあたりのDBクエリ回数 × Redisで防げなかった割合 × 1回のクエリにかかる時間」になります。

変数 意味 典型値
RPS_alb ALB到達RPS(CloudFrontでヒットせずにサーバーに届いたリクエスト数/秒) 150
PageCacheHit率 Nginx/Apacheが保存済みHTMLを返せた割合 0.9
Q_per_request 1ページ表示あたりのDBへのクエリ回数 30
Redis削減率 Redisが代わりに答えてくれた割合 0.8
T_query DBへのクエリ1回の平均所要時間(秒) 0.005

計算例)キャッシュなし vs あり

キャッシュなし(Page=0%, Redis=0%)

DBLoad = 150 × 1.0 × 30 × 1.0 × 0.005 = 22.5

Aurora db.r6g.xlarge(4 vCPU)でも完全に飽和してしまいます。
DBLoadがvCPU数(この場合は4)を超えると、 「CPUが足りず処理待ちが発生している」 状態です。

キャッシュあり(Page=90%, Redis=80%)

DBLoad = 150 × 0.1 × 30 × 0.2 × 0.005 = 0.45

vCPU 1未満。つまりDBはほぼ休んでいる状態です。ページキャッシュとRedisのかけ算効果が実感できるはずです。

解釈

  • ページキャッシュ × Redis → かけ算で効くため、両方高めると効果が劇的に大きくなる
  • クエリ最適化 → T_query削減(遅いクエリの改善は、全リクエストに波及する)
  • Q_per_request削減 → 使っていないプラグインを無効化するだけで、1ページあたりのDBクエリ数自体が減る

4. カンタンCloudWatch 5分診断

私も最初は「どこから見ればいいか分からない」となっていましたが、答えは簡単で、エッジからオリジンへ、上から順に見ることを意識すれば良いです!

順番 レイヤ メトリクス(指標名) 正常基準 赤信号 対処
1 CloudFront CacheHitRate(キャッシュで返せた割合) > 80% < 50% Cache Policy・ビヘイビア(URLパターン別のルール)確認
2 ALB TargetResponseTime(サーバー応答時間) < 200ms > 1s PHPかDBがボトルネック
3 EC2 CPUUtilization(CPU使用率) < 70% > 85% PHPワーカーが多すぎる or OPcacheが無効
4 Redis CurrConnections / Evictions(接続数 / キー削除数) Evictions = 0 Evictionsが続く メモリ(maxmemory)拡張
5 Aurora DBLoad / DatabaseConnections(DB負荷 / 接続数) DBLoad < vCPU数 DBLoad > vCPU×2 遅いクエリ特定・Read Replica追加

診断フロー

レイヤ順に見るだけで犯人は特定できます。自分の「勘」ではなく正確な 「手順」 で原因の切り分けを行いましょう!
:::message
SLAによって基準は変動します。
:::


5. PHP-FPM ワーカー設計

PHP-FPMとは、PHPのリクエストを処理するプロセスマネージャーです。「ワーカー」はその中の作業員のようなもので、ワーカーが多いほど同時にたくさんのリクエストを処理できます。

「とりあえず pm.max_children = 50」という設定をよく見かけますが、この値に根拠はありますか?

FPMワーカー数は 「メモリから決める」 が鉄則です!

① メモリ基準で決める

まず、実測します。

# PHP-FPMプロセスごとのメモリ使用量(RSS = 実際に使っているメモリ)を確認
ps -ylC php-fpm --sort:rss

例)

  • 1プロセスのピークRSS(最大メモリ使用量)60MB
  • EC2利用可能メモリ(OS + Nginx分を除く)6,000MB
6,000 ÷ 60 ≒ 100ワーカー

Nginx・OS・モニタリングエージェント用に最低15–20%の余裕を残してください。
OOM Killer(メモリ不足時にLinuxがプロセスを強制終了する仕組み)に殺されるのは、この余裕計算をサボったケースが多いです(泣)

② 理論最大RPS

RPS ≒ W × (1 / T_php)
変数 意味
W FPMワーカー数(同時に働ける作業員の数) 100
T_php 1リクエストのPHP処理時間(秒) 0.2
RPS = 100 × (1 / 0.2) = 500 RPS

つまり、Page Cacheで捕まらなかったリクエストが毎秒500件を超えると、ワーカー(作業員)が足りなくなり、リクエストが行列待ちになります。行列待ちが発生すると、ユーザーにはページの表示が極端に遅くなる(数秒〜十数秒)か、最悪の場合 502 Bad Gateway504 Gateway Timeout としてエラーが返ります。

③ 推奨 FPM 設定(www.conf)

[www]
pm = static
pm.max_children = 100
pm.max_requests = 500

; スローリクエスト検知:10秒以上かかるリクエストをログ
request_slowlog_timeout = 10s
slowlog = /var/log/php-fpm/slow.log

なぜ pm = static(固定モード)なのか dynamic(動的モード)や ondemand(オンデマンド)は、リクエストの増減に応じてワーカーを生成・破棄します。その分オーバーヘッドがかかります。アクセスパターンが安定している本番環境では、最初から全ワーカーを起動しておく static が最も応答が安定します。

pm.max_requests = 500 はPHPのメモリリーク(長時間動作するとメモリが少しずつ漏れる問題)対策。500リクエストごとにワーカーを作り直します。


6. Redis maxmemory設計

Redisは、DBへのクエリ結果をメモリ上に保存しておく「高速なメモ帳」のようなものです。maxmemoryとは、そのメモ帳に使えるメモリの上限です。

Redisを入れただけで満足していませんか?maxmemoryの設計なき運用は、「ブレーキのない車で高速道路を走る」のと同じ危険な状態です😨

基本式

maxmemory ≒ used_memory_peak × 1.5〜2

確認方法

redis-cli INFO memory | grep used_memory_peak_human

例:used_memory_peak_human: 180Mmaxmemory は 270MB〜360MB を設定。

Eviction(エビクション)の正しい読み方

Evictionとは、メモリが一杯になったときRedisが古いデータを自動的に削除することです。

十分な maxmemory を確保した上で Evictions が恒常的に > 0 なら、メモリ設計を見直しましょう!

推奨ポリシー

allkeys-lru

allkeys-lru は「メモリが一杯になったら、最も長く使われていないデータから消す」というルールです。WordPressのオブジェクトキャッシュは、削除されてもDBから再取得できるので、一時的な Eviction は問題ありません。

監視すべきCloudWatchメトリクス

メトリクス 意味 アラーム目安
Evictions メモリ不足で削除されたデータ数 持続的に > 0
CurrConnections Redisへの現在の接続数 PHPワーカー数の1.5倍超
DatabaseMemoryUsagePercentage Redisのメモリ使用率 > 80%
CacheHits / CacheMisses Redisが代わりに答えられた回数 / 答えられなかった回数 ヒット率 < 80%(※)

ElastiCacheは CacheHitRate という単一メトリクスを提供していません。CloudWatch数式 m1/(m1+m2)*100(m1=CacheHits, m2=CacheMisses)でヒット率を算出してください。


7. コスト相関モデル($ / RPS)

高速化の投資対効果を説明できますか? 「この施策でいくら安くなるか」を数式で語れるようにしましょう。

C_total ≒ C_fixed(固定費) + C_variable(変動費) × RPS × P_origin

「毎月のインフラ費用 = 固定的にかかる分 + アクセスがサーバーに届くほど増える分」です。 P_origin を下げれば、変動費が減ります。

固定費 vs 変動費の性格

コンポーネント コスト特性 削減効果
ElastiCache Redis 固定費(サーバーを立てている限り課金) DBへのクエリが減り、Auroraのサイズを下げられる
Page Cache ゼロコスト(Nginxの標準機能) P_originを劇的に下げ、変動費を潰す
CloudFront 変動費(データ転送量に応じて課金) サーバーへの転送が減り、ALB/EC2費用が下がる
Aurora 変動費(インスタンスサイズまたはACUに応じて課金) キャッシュ層で守るほど大きなインスタンスが不要に

試算例)Page Cache導入のインパクト

P_originを 15% → 1.5% に改善できた場合

  • AuroraのDBLoadが1/10に → インスタンスサイズを 1ステップダウン可能
  • EC2のCPU使用率が大幅低下 → インスタンス数削減可能
  • 追加コスト 0円(Nginxの設定のみ)

ページキャッシュは、最も投資対効果(ROI)が高い高速化施策です!


8. TTL階層設計

TTL(Time To Live)とは、 キャッシュの有効期限 です。この時間が過ぎるとキャッシュは破棄され、次のリクエストで新しいデータが取得されます。

キャッシュTTLを全レイヤで同じ値にすると、全キャッシュが 同時に期限切れ になり「雪崩」が起きます。

雪崩とは何か
たとえば全レイヤのTTLを300秒に統一したとします。300秒後、CloudFront、Page Cache、Redisのキャッシュが一斉に消えます。その瞬間にアクセスが来ると、全リクエストがキャッシュなしの状態でPHP → DB まで到達します。普段はキャッシュが守ってくれていた負荷が一気にサーバーに押し寄せ、セクション5で計算した理論最大RPSを瞬間的に超えてしまい、サイトがダウンする可能性があります。

🔑 TTL設計の最重要原則

エッジ寄り(配信レイヤ)はページ鮮度を保つため短めに、オリジン寄り(オブジェクトキャッシュ)はDB負荷軽減のため長めに設定する

この原則を無視して全レイヤ同一TTLにすると、上述の「雪崩」の原因になります。以下の設計例を参考にしてください。

レイヤ TTL 設計意図
静的アセット(CSS/JS/画像) 1年 変更時はファイル名にハッシュ付与で破棄
CloudFront HTML 600秒 ページ鮮度とエッジ配信のバランス
Page Cache 300秒 更新反映を優先しつつFPM負荷軽減
Redis(オブジェクト) 3600秒 DBクエリ結果の再利用でDB負荷削減

キャッシュ雪崩の対策

上記の雪崩を防ぐには、 「全キャッシュが同時に切れない」 仕組みを入れます。

  • TTLにジッター(ランダムなバラつき)を加える
    たとえばTTL 300秒なら、実際には280〜320秒のように少しずつズラす。こうすることで、全て同時にキャッシュ切れになるのを防ぐ
  • stale-while-revalidate
    CloudFrontで「キャッシュの期限が切れても、まず古いキャッシュをユーザーに返しつつ、裏側で新しいデータを取りに行く」設定。ユーザーには常に即座に応答を返せる
  • ロック機構
    Nginxの fastcgi_cache_lock on を使うと、同じURLへの同時リクエストのうち「1人だけがPHPに取りに行き、残りはその結果を待つ」方式になる。100人が同時にアクセスしても、PHPが動くのは 1回だけ

キャッシュ破棄戦略

記事更新時にキャッシュを破棄する設計も必須です!

レイヤ 破棄方法
CloudFront CreateInvalidation API(/article/123*のようにパス指定)
Page Cache Nginx: rm /var/cache/nginx/... またはWordPressプラグイン経由
Redis wp cache flush またはRedis Object CacheのPurge

WordPressの save_post フック(記事保存時に自動実行される処理)でこれらを連動させると、更新と同時に全レイヤのキャッシュをまとめて破棄できます。


9. セキュリティ × キャッシュ統合

キャッシュ設計とセキュリティは表裏一体です。キャッシュを誤ると、他人のダッシュボードが見えたり、ログインCookieが漏れるなどのセキュリティ事故が発生する可能性があります。

キャッシュ除外必須リスト

対象 理由 設定箇所
/wp-admin/* 管理画面はユーザー固有 CloudFront Behavior / Nginx
/wp-login.php 認証ページ CloudFront Behavior / Nginx
POSTリクエスト 状態変更 Nginx $request_method
Cookie付きページ ユーザー固有コンテンツ Nginx $http_cookie
Set-Cookieレスポンス セッション情報の漏洩 PHP側のレスポンスヘッダー制御
REST API (/wp-json/*) 動的データ(毎回内容が変わる) CloudFront Behavior

原則

  1. 個人化ページは絶対にキャッシュしない — 他ユーザーの情報が返るリスク
  2. Cookieは最小化 — 不要なプラグインのCookieがCache MISSを引き起こす
  3. WAFはCloudFront直下 — オリジン到達前に不正リクエストを排除
  4. Cache-ControlヘッダーをPHP側から明示する — 「このページはキャッシュしてよいか」をサーバー側から明確に指示する

事故事例)他人のダッシュボードが見えた

原因:wordpress_logged_in_* CookieをCloudFrontが無視し、ログイン状態のページがエッジキャッシュされた。

対策:CloudFrontのCache PolicyでCookieをキャッシュキーに含める(Cookieが違えば別のキャッシュとして扱う)か、Cookie付きリクエストはキャッシュをバイパス(素通り)する。


10. 意思決定チャート

「何から手をつければいいか分からない」という方へ。
サイト特性に応じた対策の優先順位があるので、参考にしてください。

各戦略の特徴

戦略 最適なケース 即効性 コスト P_originへの影響
① Page Cache + CF ブログ・コーポレートサイト ★★★ 無料〜低 劇的に改善
② Redis + DB最適化 会員制・ECサイト ★★ DBLoad削減
③ Read Replica 大量読み取り ★★ 直接影響なし(DB側の負荷分散)
④ OPcache + FPM 全構成共通 ★★★ 無料 間接的に改善

迷ったら、まず④(OPcache + FPM)と①(Page Cache)から。コストゼロで最大の効果が得られます。


11. セルフ診断 あなたの環境は「積んだだけ」になっていないか?

以下の10項目をYes/Noで回答してみてください。
「何を確認すればいいか」と「Noだと何が起きるか」も書いてあるので、こちらもご参考に!

キャッシュ効果を把握できているか?

# 診断項目 確認方法 Noだとどうなる? 詳細
1 CloudFrontのキャッシュヒット率を把握している CloudFrontコンソール → 「キャッシュ統計」で CacheHitRate を確認 どれだけCFが仕事をしているか分からず、改善の指標がない 2. オリジン到達率
2 P_origin(PHPまで到達するリクエストの割合)を数値で説明できる P_origin = (1 - H_cf) × (1 - H_page) を自環境の値で計算してみる 全体の負荷構造が見えず、どこを改善すべきか判断できない 2. オリジン到達率

サーバー設定に根拠があるか?

# 診断項目 確認方法 Noだとどうなる? 詳細
3 FPMワーカー数をメモリから算出している ps -ylC php-fpm --sort:rss でプロセスあたりRSSを確認し、利用可能メモリから逆算 メモリ不足でOOM Killerに殺される or 少なすぎて行列待ち発生 5. PHP-FPM ワーカー設計
4 理論最大RPSを数式で説明できる RPS = ワーカー数 × (1 / PHP処理時間) を自環境の値で計算 「何RPSまで耐えられるか」が分からず、障害時に原因の切り分けができない 5. PHP-FPM ワーカー設計

キャッシュ層は健全か?

# 診断項目 確認方法 Noだとどうなる? 詳細
5 Redis Evictionsが恒常的に0である CloudWatch → ElastiCache → Evictions メトリクスを確認 メモリ不足でキャッシュが頻繁に消え、DBに本来不要な負荷がかかり続ける 6. Redis maxmemory設計
6 Aurora DBLoadがvCPU数以下である CloudWatch → RDS → Performance Insights → DBLoadがvCPU本数のラインを超えていないか DBが処理しきれずクエリが詰まり、サイト全体がじわじわ遅くなる 3. Aurora DBLoad統合モデル
7 TTLをレイヤ別に階層設計している CloudFront・Page Cache・Redisで異なるTTLを意図的に設定しているか確認 全キャッシュが同時に切れて雪崩が起き、瞬間的にサイトダウンするリスク 8. TTL階層設計

安全性と運用は整っているか?

# 診断項目 確認方法 Noだとどうなる? 詳細
8 キャッシュ除外ルールを明示的に設定している wp-admin、wp-login.php、POSTリクエスト、Cookie付きページが除外されているか確認 管理画面やログイン情報が他人に見える重大なセキュリティ事故 9. セキュリティ × キャッシュ統合
9 施策前後のメトリクスを比較記録している CloudWatchダッシュボードで変更前後の値(CacheHitRate, DBLoad等)をスクリーンショットや記録で残している 「効果があったかどうか」を証明できず、次の改善にも繋がらない 4. カンタンCloudWatch 5分診断
10 障害時のレイヤ別診断手順がある 「遅い」と報告されたとき、CF → ALB → EC2 → DB の順に何を見るか決まっている 障害時に「どこが原因か分からない」とパニックになり、復旧が遅れる 4. カンタンCloudWatch 5分診断

判定

Yesの数 レベル 次のアクション
9〜10個 設計できている 本記事はレビュー用として活用してください!
6〜8個 土台はある Noだった項目の「詳細」列のセクションを重点的に読み、1つずつ問題を潰していきましょう
3〜5個 積んでいるだけ まず2. オリジン到達率5. PHP-FPM ワーカー設計8. TTL階層設計の順で読み直してください
0〜2個 これからの人 本記事をブックマークして、1. 全体アーキテクチャから順番に設計していきましょう

12. 実践設定付録

以下の設定は、本記事の設計思想を反映した本番運用向けテンプレートです。各設定に「なぜこの値か」の根拠を併記しています。必ず自身の環境のメモリ・トラフィックに合わせて調整してください!
:::message
本番運用を行う際は、それぞれ十分に検証を行った上で実施してください。
:::

📋 付録内ナビゲーション


OPcache(php.ini)

; ==============================================
; OPcache: コンパイル済みPHPバイトコードをキャッシュし、CPUを守る
; 対象: PHP 8.1以降
; ==============================================

opcache.enable=1
opcache.enable_cli=0
; CLI(コマンドライン)では不要。WP-CronをCLIで動かす場合もFPM経由なら影響なし

opcache.memory_consumption=256
; WordPressコア + 主要プラグイン20〜30個で約120〜180MB消費。余裕を持って256MB確保
; 確認: php -r "echo opcache_get_status()['memory_usage']['used_memory'] / 1024 / 1024 . 'MB';"

opcache.max_accelerated_files=20000
; PHPは内部で定義済みの素数セット{223,463,...,130987}から直近上位の値に丸める(20000→32531)
; WordPress + プラグイン + テーマで通常10,000〜15,000ファイル。倍程度を確保

opcache.validate_timestamps=0
; ★本番環境の鉄則:ファイル変更検知を無効化
; デプロイのたびに opcache_reset() または PHP-FPM reload で反映する運用とする
; ※ステージング環境では =1, revalidate_freq=2 が便利

opcache.revalidate_freq=0
; validate_timestamps=0 のため実質無視されるが、意図を明示するため記載

opcache.interned_strings_buffer=32
; WordPress は翻訳ファイル(.mo/.po)で大量の文字列を使う。16MBでは不足するケースがあるため32MB推奨
; 確認: php -r "echo opcache_get_status()['interned_strings_usage']['used_memory'] / 1024 / 1024 . 'MB';"

opcache.save_comments=1
; WordPress・WooCommerce・多くのプラグインがPHPDocアノテーションに依存。0にすると動作しなくなる

opcache.huge_code_pages=1
; Linux HugePages(2MBページ)を利用し、TLBミスを削減してパフォーマンス向上
; 前提: OS側で HugePages を有効化済みであること
; 確認: grep HugePages /proc/meminfo
; ※HugePages未設定の環境では =0 にするか、この行をコメントアウト

; --- PHP 8.0以降: JITコンパイル ---
opcache.jit=tracing
; tracing JIT(最も高速)。PHP 8.0〜8.3では数値形式 1255 も使えるが、
; PHP 8.4以降は文字列形式(tracing / function)のみサポート。将来互換性のため文字列で指定
; WordPress単体では効果が限定的だが、画像処理や重いループ処理があるサイトでは5〜15%の改善が見込める

opcache.jit_buffer_size=64M
; JIT用コードバッファ。WordPressでは64MBで十分(memory_consumptionとは別枠)

デプロイ時の反映手順

# 方法1: PHP-FPMリロード(graceful:処理中のリクエストを待ってから再読み込み)
systemctl reload php-fpm

# 方法2: PHP側からリセット(デプロイスクリプトに組み込む場合)
curl -s http://localhost/opcache-reset.php
# ※ opcache-reset.php は IP制限 + トークン認証をかけること

本番でやってはいけないこと: systemctl restart php-fpm はリロードと違い、処理中のリクエストを強制切断します。デプロイ時は必ず reload(graceful restart)を使ってください。


wp-config.php(Redis Object Cache)

<?php
// ==============================================
// Redis Object Cache: DBクエリ結果をキャッシュし、Auroraを守る
// プラグイン: Redis Object Cache(Till Kruss)前提
// ==============================================

define('WP_CACHE', true);

// --- 接続設定 ---
define('WP_REDIS_HOST', 'your-redis-endpoint.cache.amazonaws.com');
define('WP_REDIS_PORT', 6379);

// TLS有効化(ElastiCache の「転送中の暗号化」を有効にしている場合は必須)
define('WP_REDIS_SCHEME', 'tls');

// DB番号で論理分離(同じRedisインスタンスを複数環境で共有する場合に重要)
// 例: 本番=0, ステージング=1, 開発=2
define('WP_REDIS_DATABASE', 0);

// キープレフィックスで名前空間を分離(Blue-Greenデプロイ時にも役立つ)
define('WP_REDIS_PREFIX', 'wp_prod:');

// --- タイムアウト設定 ---
// Redisダウン時にWordPressが長時間ハングしないための安全策
define('WP_REDIS_TIMEOUT', 1);        // 接続タイムアウト(秒)
define('WP_REDIS_READ_TIMEOUT', 1);   // 読み取りタイムアウト(秒)

// --- 耐障害性設定 ---
// Redis障害時、エラーを出さずDBへフォールバックする(サイトは遅くなるが止まらない)
define('WP_REDIS_GRACEFUL', true);

// フラッシュ時に自分のプレフィックスのキーだけを消す(他環境に影響しない)
define('WP_REDIS_SELECTIVE_FLUSH', true);

// --- キャッシュTTL ---
// オブジェクトキャッシュのデフォルト有効期限(秒)
// §8 TTL階層設計に合わせ、Redis層は長めに設定
define('WP_REDIS_MAXTTL', 3600);

// --- phpredis拡張 推奨 ---
// phpredis(C拡張)はPredis(PHPライブラリ)より約2〜3倍高速
// Amazon Linux 2023: sudo dnf install php-redis
// 確認: php -m | grep redis
define('WP_REDIS_CLIENT', 'phpredis');

// --- 管理画面バナー非表示(任意) ---
define('WP_REDIS_DISABLE_BANNERS', true);

導入後の確認手順

# 1. phpredis拡張が入っているか確認
php -m | grep redis

# 2. WP-CLIでRedis接続テスト
wp redis status

# 3. キャッシュヒット率の確認(運用開始後)
wp redis report

注意 ElastiCacheの「転送中の暗号化」を有効にした場合、WP_REDIS_SCHEMEtls にしないと接続できません。逆に暗号化を無効にしている場合は tls を外してください。


Nginx FastCGI Cache(本番構成)

# ==============================================
# Page Cache: PHPを守る盾。サーバーまで届くリクエストを劇的に減らす
# Nginx 1.25+ 推奨
# ==============================================

# --- キャッシュストレージ定義(httpブロック内に記述) ---
fastcgi_cache_path /var/cache/nginx/wordpress
                   levels=1:2
                   keys_zone=WORDPRESS:200m   # メタデータ用メモリ。200MBで約160万エントリ管理可能
                   inactive=600s              # 600秒アクセスがなければ自動削除
                   max_size=1g                # ディスク使用量上限
                   use_temp_path=off;         # 一時ファイルを経由せず直接書き込み(I/O削減)

fastcgi_cache_key "$scheme$request_method$host$request_uri";

# --- キャッシュスキップ判定(httpブロック内に記述) ---
# ログイン中ユーザー・WooCommerce利用者はキャッシュを通さない
map $http_cookie $skip_cache {
    default         0;
    ~*wordpress_logged_in_   1;   # ログイン済み
    ~*wp-settings-           1;   # 管理画面カスタマイズ中
    ~*wordpress_no_cache     1;   # キャッシュ無効化Cookie
    ~*woocommerce_cart_hash  1;   # WooCommerceカート保持中
    ~*woocommerce_items_in_cart 1; # WooCommerceカートに商品あり
    ~*wp_woocommerce_session_ 1;  # WooCommerceセッション
}

map $request_method $skip_cache_method {
    default 0;
    POST    1;   # POSTは状態変更なのでキャッシュしない
}

# --- Upstreamの定義 ---
upstream php-fpm {
    server unix:/run/php-fpm/www.sock;   # UNIXソケット推奨(TCP localhostより低レイテンシ)
    keepalive 32;                         # FPMへの接続を使い回す。ワーカー数の1/3程度
}

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php;

    # --- セキュリティヘッダー ---
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # --- Gzip(ALBの背後でもNginx側で圧縮する場合) ---
    gzip on;
    gzip_comp_level 5;           # 5が圧縮率と速度のバランス最良
    gzip_min_length 256;
    gzip_types text/css application/javascript application/json
               text/xml application/xml image/svg+xml;
    gzip_vary on;

    # --- 静的アセット(§8 TTL階層設計:1年) ---
    location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot|webp|avif)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
        access_log off;   # 静的ファイルのアクセスログを無効化(I/O節約)
    }

    # --- WordPress管理画面・ログイン(キャッシュ完全除外) ---
    location ~* ^/(wp-admin/|wp-login\.php) {
        add_header Cache-Control "no-store, no-cache, must-revalidate" always;

        fastcgi_pass php-fpm;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # 管理画面はタイムアウトを長めに(プラグイン更新・エクスポート等)
        fastcgi_read_timeout 300s;
    }

    # --- WordPress REST API(動的データのため原則キャッシュしない) ---
    location ~* ^/wp-json/ {
        add_header Cache-Control "no-store" always;

        fastcgi_pass php-fpm;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # --- メインのPHP処理(FastCGI Cache適用) ---
    location ~ \.php$ {
        # セキュリティ:存在しないPHPファイルへのアクセスを拒否
        try_files $uri =404;

        fastcgi_pass php-fpm;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # --- FastCGI バッファ設定 ---
        fastcgi_buffers 16 16k;           # バッファ数×サイズ。大きなページ用
        fastcgi_buffer_size 32k;          # レスポンスヘッダー用バッファ
        fastcgi_busy_buffers_size 48k;

        # --- キャッシュ設定 ---
        fastcgi_cache WORDPRESS;
        fastcgi_cache_valid 200 300s;     # §8 TTL階層設計:Page Cache = 300秒
        fastcgi_cache_valid 301 302 60s;
        fastcgi_cache_valid 404 10s;

        fastcgi_cache_bypass $skip_cache;
        fastcgi_cache_bypass $skip_cache_method;
        fastcgi_no_cache $skip_cache;
        fastcgi_no_cache $skip_cache_method;

        # キャッシュに2回以上アクセスされたURLだけキャッシュする(一度きりのクロールを除外)
        fastcgi_cache_min_uses 2;

        # --- 雪崩対策(§8 参照) ---
        # 同じURLへの同時リクエストを「1人だけ取りに行く」方式に集約
        fastcgi_cache_lock on;
        fastcgi_cache_lock_timeout 5s;
        fastcgi_cache_lock_age 3s;        # ロック取得者が3秒以内に返さなければ次のリクエストも通す

        # --- stale-while-revalidate(§8 参照) ---
        # PHP-FPMがエラーやタイムアウトになった場合、古いキャッシュを返す(503防止)
        fastcgi_cache_use_stale error timeout updating
                                http_500 http_502 http_503 http_504;
        # バックグラウンドで新しいキャッシュを取得(ユーザーは待たない)
        fastcgi_cache_background_update on;

        # PHPが Set-Cookie を返してもキャッシュを保存する(匿名ページ用)
        # ※ログインユーザーは $skip_cache で除外済みなので安全
        fastcgi_ignore_headers Cache-Control Expires Set-Cookie;

        # キャッシュHIT/MISS/BYPASSをレスポンスヘッダーに出力
        add_header X-Cache-Status $upstream_cache_status always;
    }

    # --- WordPress パーマリンク ---
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # --- セキュリティ:wp-config.php等へのアクセスを拒否 ---
    location ~* /(?:wp-config\.php|readme\.html|license\.txt|xmlrpc\.php)$ {
        deny all;
    }

    # --- セキュリティ:隠しファイルへのアクセスを拒否 ---
    location ~ /\. {
        deny all;
    }
}

運用コマンド

# キャッシュの手動クリア
rm -rf /var/cache/nginx/wordpress/*

# キャッシュHIT率の確認(アクセスログから集計)
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# ※ログフォーマットに $upstream_cache_status を追加しておくこと

Apache mod_cache(本番構成)

Apache環境ではNginxの fastcgi_cache 相当の機能を mod_cache + mod_cache_disk で実現します。

# ==============================================
# Page Cache: Apache版
# 前提: mod_cache, mod_cache_disk, mod_headers, mod_rewrite が有効
# ==============================================

# --- キャッシュストレージ設定 ---
CacheRoot /var/cache/apache/wordpress
CacheDirLevels 2
CacheDirLength 1

CacheEnable disk /
CacheDefaultExpire 300
CacheMaxExpire 600
CacheLastModifiedFactor 0.1

# --- 雪崩対策:同じURLへの同時リクエストを集約 ---
CacheLock on
CacheLockPath /tmp/mod_cache-lock
CacheLockMaxAge 5

# --- stale-while-revalidate:エラー時に古いキャッシュを返す ---
CacheStaleOnError on
CacheStoreExpired on

# --- デバッグ用:キャッシュHIT/MISSをレスポンスヘッダーに出力 ---
CacheHeader on

# --- キャッシュ除外:管理画面・ログイン・REST API ---
<LocationMatch "^/(wp-admin/|wp-login\.php|wp-json/)">
    Header set Cache-Control "no-store, no-cache, must-revalidate"
    CacheDisable on
</LocationMatch>

# --- キャッシュ除外:POSTリクエスト ---
<If "%{REQUEST_METHOD} == 'POST'">
    CacheDisable on
</If>

# --- キャッシュ除外:ログインCookieを持つユーザー ---
# mod_rewriteで環境変数を設定し、CacheDisableで参照
RewriteEngine On
RewriteCond %{HTTP_COOKIE} wordpress_logged_in_ [OR]
RewriteCond %{HTTP_COOKIE} woocommerce_cart_hash [OR]
RewriteCond %{HTTP_COOKIE} woocommerce_items_in_cart
RewriteRule .* - [E=NO_CACHE:1]

<If "reqenv('NO_CACHE') == '1'">
    CacheDisable on
    Header set Cache-Control "no-store, no-cache, must-revalidate"
</If>

# --- セキュリティヘッダー ---
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"

# --- セキュリティ:wp-config.php等へのアクセスを拒否 ---
<FilesMatch "^(wp-config\.php|readme\.html|license\.txt|xmlrpc\.php)$">
    Require all denied
</FilesMatch>

# --- 静的アセット:1年キャッシュ ---
<FilesMatch "\.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?|ttf|eot|webp|avif)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

Nginx vs Apache
本記事のアーキテクチャでは Nginx FastCGI Cache を推奨します。Apache mod_cache_disk はディスクI/Oに依存するのに対し、Nginx FastCGI Cacheはイベント駆動モデルにより少ないリソースで高い同時接続を処理できます(※キャッシュ本体はディスク保存ですが、メタデータを keys_zone でメモリ管理するため高速にルックアップできます)。Apache環境では、リバースプロキシとして NginxをApacheの前段に置く構成 (Nginx → Apache → PHP-FPM)も検討してください。


CloudFront Behavior 設計(本番構成)

CloudFrontではURLパターンごとにBehavior(振る舞い)を分けるのが本番運用の基本です。「1つのBehaviorで全URLを処理」は設計ミスの温床になります。

Behavior一覧(優先順位順)

優先順 パスパターン 用途 Cache Policy TTL
1 /wp-admin/* 管理画面 CachingDisabled -
2 /wp-login.php ログイン CachingDisabled -
3 /wp-json/* REST API CachingDisabled -
4 /wp-content/uploads/* 等(※) 静的アセット CachingOptimized(AWS Managed) Max 86400秒
5 *(デフォルト) 動的HTML カスタムポリシー(下記) Default 600秒

CloudFrontのBehaviorは 1つにつき1つのパスパターン しか設定できません。*.css のような拡張子ワイルドカードを使う場合は拡張子ごとにBehaviorを作成する必要があります。実運用では /wp-content/*/wp-includes/* のようにパスプレフィックスで一括する方が管理しやすくなります。

動的HTML用カスタム Cache Policy

設定項目 理由
Min TTL 0秒 オリジンの Cache-Control: no-store を尊重するため
Default TTL 600秒 8. TTL階層設計に準拠。オリジンがCache-Controlを返さない場合のフォールバック
Max TTL 600秒 Default TTL と同じにし、意図しない長期キャッシュを防止
Cookie なし(None) キャッシュキーにCookieを含めない。ログインユーザーの /wp-admin /wp-login.php はBehavior1、2でバイパス済み。注意: ログインユーザーがフロントエンド(一般ページ)を閲覧する場合はBehavior5(デフォルト)にマッチし、匿名キャッシュが返される。会員制サイト・ECサイトでは Cookie を wordpress_logged_in_* でキャッシュキーに含めるか、Origin Request Policy経由でオリジン側で Cache-Control: no-store を返す設計が必要
Query String 全て含める(All) WordPressのページネーション(?paged=2)やUTMパラメータの区別に必要
Header なし(None) Hostはオリジン側が処理。Accept-Encoding等はCloudFrontが自動圧縮

Origin Request Policy(オリジンに転送する情報)

Cache PolicyとOrigin Request Policyは別ものです。Cache Policyはキャッシュキーを決め、Origin Request Policyはオリジンに何を転送するかを決めます。

設定項目 理由
Cookie wordpress_logged_in_*, wp-settings-*, wordpress_sec_* ログイン判定に必要なCookieだけをオリジンに転送
Header Host, CloudFront-Forwarded-Proto バーチャルホスト判定とHTTPS判定に必要
Query String 全て WordPressが使用するクエリパラメータをすべて転送

Response Headers Policy(レスポンスヘッダー制御)

CloudFront側でセキュリティヘッダーを一元管理すると、Nginx/Apache側の設定漏れを防げます。

ヘッダー 効果
Strict-Transport-Security max-age=31536000; includeSubDomains HTTPS強制(HSTS)
X-Content-Type-Options nosniff MIMEスニッフィング防止
X-Frame-Options SAMEORIGIN クリックジャッキング防止
Content-Security-Policy サイトに応じて設定 XSS・データ注入防止(XSS対策の本命)

補足
X-XSS-Protection ヘッダーはChrome 78(2019年)でXSS Auditorが削除されて以降、主要ブラウザでは機能しません。OWASPも現在は設定を推奨していないため、本記事では掲載していません。XSS対策は Content-Security-Policy で行ってください。

静的アセット用のBehaviorには AWS Managed Policy の CachingOptimized(ID: 658327ea-f89d-4fab-a63d-7e88639e58f6)がそのまま使えます。自分でポリシーを作る必要はありません。


13. 要点まとめ

# ポイント 根拠
1 肝は P_origin 性能もコストも「PHPまで到達する割合」に帰着する(2. オリジン到達率
2 ページキャッシュは構造変換 ゼロコストでサーバー負荷を1/10以下に(7. コスト相関モデル
3 DBLoadは数式で語れる 「なんとなく重い」を具体的な数値に(3. Aurora DBLoad統合モデル
4 PHPワーカーはメモリから設計 根拠なき設定はメモリ不足の元(5. PHP-FPM ワーカー設計
5 測定しない高速化はただの願望 前後比較なき施策は再現できない(4. CloudWatch 5分診断11. セルフ診断

結論

WordPress高速化とは、キャッシュを入れることではありません。

再現可能で、測定可能で、事故らない設計をすること。

が根本的な解決へ導きます。

本記事で提示した数式・診断フロー・設定テンプレートを使えば、「なぜこの設定なのか」を各ステークホルダーに説明できるはずです。

キャッシュは積むな、設計せよ!!😼


本記事の内容は2026年2月時点の情報に基づいています。AWSサービスの仕様は変更される可能性があるため、公式ドキュメントも併せてご確認ください。

誤植、誤りなどありましたらご指摘ください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?