CDN キャッシュ#
HTTP キャッシュと ETag で宣言した Cache-Control は、そのまま CDN(Cloudflare 等)のエッジキャッシュにも効きます。読み取り中心のサイトなら、エッジでヒットさせてオリジン(PHP / リモート DB)への到達をほぼゼロにできます。ただし HTML を CDN に載せるには CDN 側の設定が要る こと、そして Relayer 特有の「セッション Cookie で全 BYPASS」する罠があるので、その 2 点を中心にまとめます。
ブラウザと CDN を分ける(max-age と s-maxage)#
Cache の maxAge はブラウザ、sMaxAge は共有(CDN)キャッシュの鮮度です。 ブラウザキャッシュはパージできない(一度配ると修正が届かない)一方、 CDN はパージできるので、非対称に振るのが定石です。
// ブラウザは短く、CDN は長く。編集は CDN パージで即時反映できる
new Cache(public: true, maxAge: 300, sMaxAge: 2592000, etag: $etag);
// → Cache-Control: public, max-age=300, s-maxage=2592000共有ポリシーは 1 か所(例: App\PageCache::timed())に集約し、TTL をそこだけのノブにしておくと運用が楽です。
Cloudflare で HTML をキャッシュさせる#
Cloudflare は 既定では静的アセット(拡張子ベース)しかキャッシュせず、 HTML / JSON は「動的」扱いで素通りします。s-maxage を付けてもそれだけでは効きません。対象パスに Cache Rule(旧 Page Rule の "Cache Everything" 相当=Eligible for cache)を作り、Edge TTL を オリジン尊重(Use origin cache-control) にします。これで Cloudflare は s-maxage を採用し、エッジで指定秒数キャッシュします。
- 対象は読み取り系すべて(例:
/,/docs/*,/search,/api/*)。
ルールのパス範囲が一部だけだと、その外のページは延々 MISS のままキャッシュされません。
- 編集の反映を CDN パージ運用にするなら、デプロイ後にゾーンを
パージする(CI から Cloudflare API の purge_everything を叩く等)。
最大の罠: セッション Cookie で全 BYPASS#
Cloudflare は Set-Cookie を含むレスポンスを問答無用でキャッシュ回避 します(cf-cache-status: BYPASS)。ここで Relayer 特有の落とし穴があります。
Relayer は各ページを use-php コンポーネントとして描画し、AppRouter はページルートの ComponentState を 既定 StorageType::Session で生成します。useState を一切使わない静的なページでも、この Session ストレージが作られます。ただし SessionStorage は遅延化されており、 session_start() は状態に初めてアクセスした時だけ走ります。状態を読み書きしないページはセッションを開始せず、Set-Cookie: PHPSESSID が出ないのでエッジキャッシュ可能です。状態を持つページ(useState/ auth/CSRF)だけが Set-Cookie を出し、その分は意図どおり BYPASS されます。
ポイント:
- アプリ側で
session.use_cookies=0を立てて塞がない。CDN は通っても、
後で auth / CSRF / useState を使うページを黙って壊します。直し方は「フレームワークに任せる」が正解。
- 状態を持たない共有コンポーネントは
fc(..., StorageType::Memory)に
しておくと、既定 Session に引きずられず保険になります(Memory はセッション非接触。Snapshot は USEPHP_SNAPSHOT_SECRET 未設定だと本番で失敗するので、状態ゼロ用途には Memory が無難)。
- 別件で
session.cache_limiterは空にしておく。セッションが開始されると
PHP が Cache-Control: no-store, no-cache, must-revalidate を注入し、ページごとのキャッシュ方針を上書きしてしまうためです。
// public/index.php、Relayer::boot() より前
\ini_set('session.cache_limiter', '');効いているかの確認#
レスポンスヘッダを実測します。
curl -sI https://example.com/docs/some-page | \
grep -iE 'cache-control|cf-cache-status|age|set-cookie'set-cookie:が無いこと(あれば BYPASS の原因)。cf-cache-status:がMISS→ 再アクセスでHIT、age:が増えていく。cache-control:に意図したs-maxageが乗っていること。
DYNAMIC は Cache Rule 未設定(HTML 非対象)、BYPASS は Set-Cookie 等でキャッシュ除外、を意味します。ゾーンをパージした直後はエッジ未投入でしばらく MISS が続くので、数十秒おいて再確認してください。
編集の反映とパージ#
max-age / s-maxage の内側ではオリジンへ問い合わせないため、コンテンツを更新しても CDN は最大 s-maxage 秒だけ古いままになります。デプロイ時にゾーンをパージする CI を組んでいればデプロイ反映は即時ですが、デプロイを伴わずに内容だけ更新する運用(外部 DB を直接編集する等)では、その更新はパージするまで CDN に出ません。長い s-maxage を使うなら、内容更新の経路にも CDN パージを必ず組み込んでください。
変更履歴 (3)
- サイドバー再構成: 開発カテゴリ追加に伴う order 調整
- use-php 0.7.0/0.7.1・relayer ^0.12.1 の依存バージョン記述を削除し SessionStorage 遅延化を現在形で説明
- 新規作成