キャッシュは応答時間を短縮し、データベースや外部サービスへの負荷を軽減できます。しかし、非効率なクエリや境界の定義が曖昧なアーキテクチャを、それだけで改善できるわけではありません。PHPのキャッシュ戦略では、何を保存するか、誰が利用できるか、どのくらい有効か、ストレージ機構に障害が発生した場合にどうするかを明確にする必要があります。
成功の基準は、ヒット率を上げることだけではありません。キャッシュが空、古い、または利用不能な状態でも、アプリケーションはビジネスルールを維持しなければなりません。そのためには、まず問題を測定し、キャッシュを一次的な信頼できるデータソースではなく、動作を検証できる最適化として扱うことが重要です。
保存するデータを決める前に測定する

まず、遅いルートとその原因を特定します。エンドポイントごとの応答時間、クエリのレイテンシ、外部サービスへの呼び出し、CPU使用率、リクエスト数を記録しましょう。平均値と上位パーセンタイルは分けて確認します。平均値が許容範囲でも、一部のユーザーに非常に遅い応答が発生している可能性があります。
需要に比べて結果が変わる頻度の低い、繰り返し実行される処理を探します。たとえば、カタログ、公開設定、公開済みコンテンツ、計算コストの高い集計などです。また、インデックスのないクエリ、リクエスト過多、シリアライズ、同一リクエスト内での処理の重複が遅延の原因になっていないかも確認します。無効化が難しい結果をキャッシュするより、クエリを改善するほうが適切な場合もあります。
変更前に、性能、負荷、観測されたデータ品質の基準値を定めます。そのうえで、同じトラフィックまたは再現可能なテストと比較します。改善がキャッシュによるものだと判断できなければ、運用コストや副作用を評価するのは困難です。
アプリケーション、HTTP、クエリのキャッシュを選ぶ
アプリケーションキャッシュは、クエリの結果や計算済みのレスポンスなど、PHPコードで再利用できる値を保存します。共有メモリ、専用サービス、またはアプリケーションと互換性のある別のバックエンドに配置できます。複数のリクエストで同じ結果が必要になり、その適用範囲と有効期間を正確に定義できる場合に適しています。
HTTPキャッシュでは、Cache-ControlやETagなどのディレクティブやバリデーターを使い、ブラウザーや中間プロキシがレスポンスを再利用できるようにします。公開リソースや、プライバシーポリシー上保存が認められるレスポンスに有効です。個別化されたレスポンスには特に注意が必要です。publicポリシーはデータを露出させるおそれがあり、privateまたは保存不可のポリシーは、関係するフローや中間プロキシによってはその可能性を低減できます。
クエリキャッシュは、データベース、ドライバー、または結果を保持するレイヤーの機能として提供される場合があります。データアクセスの最適化と混同してはいけません。キャッシュは、インデックス、適切なクエリプラン、トランザクションの代わりにはなりません。有効化する前に、誰がエントリーを無効化するのか、書き込み後の読み取りに即時反映が必要かを把握しましょう。
すべてを一度に導入するのではなく、問題に最も近いレイヤーを選びます。明確なモデルなしにキャッシュを重ねると、複数のレイヤーに同じデータの異なるバージョンが残り、問題の診断が難しくなることがあります。
キー、適用範囲、有効期限を設計する
キーには、結果を変えるすべての要因を含める必要があります。たとえば、カタログのレスポンスは、言語、通貨、地域、データのバージョンに依存する場合があります。診断しやすいよう、それらの要素を安定した読みやすい形式で含めます。機密情報をキーに含めることは避けてください。
結果がユーザー、ロール、権限、組織に依存する場合は、それに応じて分離しなければなりません。ルート名だけを含むキーでは、個別化されたレスポンスが別の人に返される可能性があります。プロセス間やサーバー間でエントリーがどのように共有されるかも確認してください。結果に影響するすべての要素を確実に列挙できない場合、そのレスポンスを共有キャッシュに保存してはいけません。
有効期限は、恣意的な値ではなくビジネスルールに基づいて決めます。変更頻度の低い設定なら、口座残高や予約状況よりも長い有効期間を許容できる場合があります。許容可能な最大の陳腐化期間を定め、類似したエントリーが同時に期限切れになる可能性がある場合は、有効期限にランダムなばらつきを持たせることも検討しましょう。
時間による期限切れは、重要な変更に伴う無効化の代わりにはなりません。エントリーが無期限に残らないようにする最終的な上限であり、即時更新を保証するものではありません。
元データが変わったときに更新・無効化する
必要な整合性に合った仕組みを選びます。明示的な無効化は書き込み後に実行できます。直接的な方法ですが、データ保存後、キャッシュ削除前に処理が中断すると失敗する可能性があります。別の方法として、キーにバージョンを含める方法があります。元データが変わったときにバージョンをインクリメントすれば、以前のエントリーは参照されなくなります。ただし、バージョンを適切に管理する必要があります。
イベント駆動型のシステムでは、変更の確定後に通知を発行し、無効化または派生データの再計算を行えます。イベント配送の信頼性が十分でない場合は、再試行、冪等性、照合・修復の手段を用意してください。データベースとキャッシュサービスの更新がアトミックな操作になると想定してはいけません。
短期間の陳腐化を許容できるデータでは、期限切れのエントリーを提供しながらバックグラウンドで更新する方法が適切な場合があります。ただし、すべての情報に使えるわけではありません。対象となるデータ、陳腐化を許容する期間、更新に失敗した場合の動作を定めます。以前の値を表示する影響を踏まえて判断してください。
障害時の安全な動作を定義する
エントリーがない状態、期限切れの状態、バックエンドが利用できない状態の3つをテストします。それぞれについて、元データから再計算するのか、制御されたエラーを返すのか、安全な場合に以前の値を返すのかを決めます。キャッシュが空であることは起動時の通常状態であり、誤った結果につながってはなりません。
バックエンド障害時には、元データを保護してください。同じキーが期限切れになった際、すべてのリクエストが高コストなクエリを実行すると、負荷が急増するおそれがあります。再計算の同時実行数を制限する、安全な値を一時的に再利用する、上限やタイムアウトを設定するといった対策が考えられます。適切な対策は、データの陳腐化を許容できるか、元データにどれだけの余力があるかによって異なります。
機密値を含めずに、エラー、レイテンシ、読み書きの失敗を記録します。キャッシュは通常、データの権威ある保存先ではなく性能上の依存先です。その利用不能によって重要な処理を停止させる必要があるなら、その判断を明示的に正当化しなければなりません。
再現可能なテストで整合性と性能を検証する
キャッシュ無効、コールド、ウォームの各シナリオを比較します。書き込み後の最初のリクエスト、言語や権限の変更、同時の期限切れ、バックエンド障害も含めてください。内容だけでなく分離も確認します。権限の異なる2人のユーザーに、互いのレスポンスが返ってはいけません。
キーと無効化に関する重要なルールには自動テストを使い、実際のバックエンドまたは代表的な代替手段を使った動作は統合テストで検証します。負荷テストでは、ヒットとミス、キャッシュのレイテンシ、再計算の回数、元データへの負荷、エラーを観察してください。ヒット率が高くても、レスポンスが正しいとは限りません。
変更をデプロイした後は、基準値とメトリクスやエラーを比較します。古いデータ、メモリの増加、データベースへの予期しない負荷が発生した場合に備え、最適化を無効にする、または安全な動作に戻す手段を用意しておきましょう。
キャッシュ運用チェックリスト

- 問題を測定済み:キャッシュを正当化するルートと処理が特定されている。
- データと正本を定義済み:キャッシュを一次記録として扱っていない。
- キーが完全:関連する機能上の要素と権限の要素が含まれている。
- 有効期限に根拠がある:陳腐化に対するビジネス上の許容度と一致している。
- 無効化の方法を定義済み:各エントリーを更新または置き換えるイベントが明確になっている。
- 障害時の動作を定義済み:キャッシュが空、期限切れ、利用不能な場合の動作が定まっている。
- 運用状況を測定:レイテンシ、エラー、ヒット、元データへの負荷を監視している。
- 分離テストを実施:ユーザー、ロール、言語、その他の関連コンテキストを検証している。
堅牢なキャッシュ戦略は、データの意味を意図せず変えることなく、繰り返しの処理を減らします。各エントリーの適用範囲、有効期間のポリシー、検証可能な障害時の計画が明確であれば、チームはメリットを評価し、最適化が適切でなくなったときに調整または撤去できます。



