Cacheは何を構築する必要がありますか?



「何を、どのように、どこでキャッシュするか」というトピックに関する良い記事がいくつかあります。 では、なぜこのトピックを再び先延ばしにするのでしょうか? また、このトピックは非常に重要であり、多くの場合、特定の問題に遭遇するまで、それを扱う必要があるとは考えないでください。 ですから、私が期待している聴衆は、既存の記事が出版される頃には興味がなかった人たちですが、今では興味があり、彼らは通り過ぎません。



キャッシングを整理する際の主なポイントを簡単に強調してから、開発者の生活を簡素化する.Net Framework 4.0の革新を検討します(ASP.NETインフラストラクチャ外のインメモリキャッシュについて説明します)。



エントリー



多くの場合、パフォーマンスに関して言えば、キャッシュ手法を使用しないと実行するのは非常に困難です。 ただし、効果的に適用する前に、次の質問に答える必要があります。



興味深いことに、答えはこれらの質問がリストされている正確な順序で与えられなければなりません。 というのは、「何をどのように」キャッシュするかを理解するのではなく、「どこにキャッシュするか」と言うのは難しいからです。 それでも、システム設計の初期段階でキャッシュを処理することを強くお勧めします。 「キャッシュはいつでも最後に追加できる」という一般的な信念に反して、多くの場合そうではありません。 初期段階でキャッシュについて考えなければ、キャッシュを追加してテストすることは非常に困難です。 上記の質問に対する答えを見つけようとしますが、RAMに保存されている.Netアプリケーション内の汎用キャッシュに関して、以下の考えのほとんどが与えられることをすぐに明確にしたいと思います。 プロセッサキャッシュまたはブラウザキャッシュではありません。 さらに、1つの記事の枠組みの中で、キャッシングのすべての可能な理論を詳細かつ明確に説明することは非常に難しいため、一般的な間違いを避けるのに役立つ基本的な推奨事項とヒントを示します。



なに? どうやって? どこ?



キャッシュすることを決定した場合、キャッシュがフリーではなく、すべてのデータタイプに役立つわけではないことを理解する必要があります。 選択したキャッシュ戦略とキャッシュ実装に関係なく、データの陳腐化とそれらが占有するメモリ量の問題を何らかの形で明らかにします。 また、キャッシュはアプリケーションの作成/テスト/メンテナンス中に複雑さを増すため、すべてを連続してキャッシュする必要はありません。 問題に加えてキャッシュを追加してもメリットが得られる場所を選択するには、バランスの取れたアプローチを取る必要があります。



リアルタイムでのみ有用なデータをキャッシュしないでください。 たとえば、取引システムの通貨相場が30秒期限切れになると、意思決定の正確性に大きく影響する可能性があります。 しかし、ホームページのシステムで先週の販売統計の概要がある場合、このデータはキャッシュに最適です。



また、データソースから取得できるものをすぐにキャッシュしたり、オンザフライで計算したりすることは意味がありません。 ただし、データソースが非常に遅く、データの特異性により多少の遅延が生じる場合、これらはキャッシュに適した候補です。



別の例として、オンザフライで計算されるデータを見てみましょう。大量のプロセッサ時間が必要ですが、結果は非常に大量です。 このデータをキャッシュしようとすると、使用可能なすべてのメモリがすぐにいっぱいになり、そこにいくつかの結果しか配置されません。 これらの条件下では、次のように効果的なキャッシュ操作を実現することは困難です。 ほんの数個の新しい要素が計算されたばかりの値の上書きにつながり、成功したヒット(キャッシュ内の目的の要素を見つける)の割合が非常に低くなる可能性があります。 この場合、結果をキャッシュするのではなく、計算アルゴリズムを高速化することを検討する必要があります。 キャッシュに保存する適切な候補を選択するときは、キャッシュの有効性を常に考慮してください。 つまり 選択したデータは、キャッシュに配置されたときに、新しいデータによってキャッシュからプッシュされたり、古くなったりする前に何度も抽出されるようにする必要があります。



データをキャッシュに適切に保存する方法を考えると、次の点に注意する必要があります。



「保管方法」という質問に関連するいくつかの問題は非常に複雑になる可能性があるため、それらを解決するために個別のプロジェクトが作成され、関連する経験を持つ専門家が区別されます。 先ほど言ったように、この記事はキャッシュの問題に関連する深さに影響しないので、これがあなたのプロジェクトに当てはまらないことを願っています。



したがって、「What」および「How」の質問に対する回答を受け取った後、質問に対する回答が、アプリケーションで作成された辞書<T、T>になることが判明する場合があります。 もしそうなら、私たちは非常に幸運です。 しかし、原則として、すべてが少し複雑になり、完全なキャッシュ実装を記述するか、既成のソリューションのいずれかを選択する必要があります。

注:辞書ベースの実装がキャッシュと見なされるかどうかについては意見が一致していません。 個人的に、私はこれを特別なケースと見なすことを好みます。 同時に、そのようなキャッシュを「静的」と表現する用語に出くわしました。 データが削除されず、無限に関連があると見なされるキャッシュ。



手書きキャッシュ



キャッシュの書き方は教えません。 それどころか、私はこれが簡単で単純であるという誤った印象からあなたを保護しようとします。 辞書のような実装が私たちのニーズを完全にカバーしていない限り、完全なキャッシュを書くことは非常に困難です。



頭に浮かぶ最初の問題は、マルチスレッド環境で作業することです。 結局、キャッシュを使用する場合、システムが小さくなく、1つのスレッドで作業することは非常に非効率的です。 つまり すべてのデータの書き込み/読み取り/無効化操作はスレッドセーフでなければなりません。 スレッドの使用経験が豊富ではないため、スレッド同期アプローチが最適に選択されていないため、デッドロックまたは低速動作が保証されます。



データの陳腐化がどのように発生するかを考えると、キャッシュがサポートすべき最も単純なシナリオとはほど遠いものが思い浮かぶでしょう。 可能なすべてのオプションのデカルト積を取得すると、システムが取り得る状態が多数得られます。 これは、自己記述キャッシュのデバッグとテストのタスクを単純に耐えられないものにするための十分な基盤になります。



インターネットで見られる多くのキャッシングの例では、弱参照メカニズムを使用しています。 それらを実装に適用したいという制御不能な欲求が現れるかもしれません。 しかし、関連分野での十分な経験がなければ、ほとんどのチームが理解できないだけでなく、最初の5回の書き換え後に動作する可能性が低いコードを取得する可能性をわずかに増やすことはありません。

私はこのリストを長い間続けることができると思いますが、既にリストされている理由でさえ、あなたが強さと忍耐力についてあなた自身をテストする欲求を失うのに十分であることを望みます。 そうでない場合は、「強さ、心、忍耐(C)」のみを望みます。



キャッシュが簡単ではないことに気付いたので、記事の最後の部分に移ることをお勧めします。これは、.NET Frameworkに私たちの生活を簡素化する準備が既に整っていることを伝えるものです。



.Net Framework 4.0以前の生活



キャッシュは常にASP.NET Webアプリケーションの不可欠な部分であり、.Net FrameworkはASP.NETアプリケーションに優れたツールを提供してきました。 したがって、歴史的に、キャッシュを操作するためのすべてのクラスはSystem.Webアセンブリにありました。 キャッシュがWebの外部(Windowsサービスなど)で必要になると、多くの開発者はソリューションの美しさを犠牲にし、System.Webアセンブリへのリンクを追加しました。 これにより、キャッシュを活用できましたが、大量の不必要なコードを引き付けました。 この問題は長い間未解決のままでしたが、幸いなことに、.NET Framework 4.0では彼らはそれにもどりました。 その結果、 System.Runtime.Caching名前空間を取得しました。この名前空間には、とりわけ抽象クラスObjectCacheがあり、その唯一の実装はMemoryCacheです。 彼らと一緒にあなたを紹介したいと思います。



オブジェクトキャッシュ



ObjectCacheは、さまざまなキャッシュ実装で作業する際のアプローチを標準化できるようにする抽象クラスです。 キャッシュを操作するための同じインターフェース(API)があるため、新しい各キャッシュ実装を詳細に検討する必要はありません。 実際、ユーザーの観点から見ると、実装は同じように見え、このクラスのAPIの形式で表現された既知の期待に従って動作する必要があります。 主なメソッド、プロパティ、およびそれらの目的を以下に示します。



プロパティ



方法



一般的に言えば、独自のキャッシュ実装を作成する方法がすでに明確になっていることを願っています。 しかし、さらに詳しく検討したい点がいくつかあります。つまり、キャッシュにデータを追加する方法です。 既存のキャッシュ実装であるMemoryCacheクラスのメソッドの例を使用して、これを行うことを提案します。



メモリキャッシュ



名前が示すように、MemoryCacheはRAMにデータを保存する実装です。 現時点では、これはObjectCacheを継承する.Net Frameworkの唯一のクラスですが、他の実装を提供するNugetパッケージがあります(たとえば、 SqlCache Nugetパッケージを使用して、 Sqlサーバーにデータを保存できます)。 以下では、操作がすぐに明らかではない可能性があるメソッドのみを検討します。 メソッドがどのように機能するかを示すために、 xUnitを使用して記述された単体テストリストが表示されます。



AddOrGetExistingメソッド(...)


キーがまだ使用されていない場合にのみアイテムを追加します。そうでない場合は、新しい値を無視して既存の値を返します。





メソッドを追加(...)


AddOrGetExisting(...)のラッパーであり、ほぼ同じように機能しますが、唯一の違いは、アイテムが正常に追加された場合はTrueを返し、キーが既に存在する場合はFalseを返すことです(つまり、値の追加が発生しません)。





設定方法(...)


既存のキーをチェックせずに、新しいアイテムを追加するか、既存のアイテムを置き換えます。 つまり AddメソッドやAddOrGetExistingメソッドとは異なり、Setメソッドに渡された値は常にキャッシュに表示されます。





MemoryCacheの領域


MemoryCacheにデータを追加するすべてのメソッドには、regionパラメーター( example1example2 、およびexample3 )を受け入れるオーバーロードがあります。 しかし、NULL以外の値を渡そうとすると、NotSupportedExceptionが発生します。 誰かがこれがLiskの置換原則に違反していると言うかもしれません(そのためL id)が、そうではありません。 結局のところ、地域の機会を利用する前に、クライアントコードは特定の実装で実装されていることを確認する必要があります。 これは、対応するビットフラグ(DefaultCacheCapabilities.CacheRegions)の存在についてDefaultCacheCapabilitiesプロパティをチェックすることで行われ、MemoryCacheには設定されません。



CacheItemPolicy





データを追加するすべての方法を示すために、キー、値、および値が関連すると見なされるまでの時間を取る、最も単純なオーバーロードのバージョンが選択されました。 しかし、それらはすべて、タイプCacheItemPolicyのパラメーターを受け入れるバージョンも持っています。 このパラメーターのおかげで、キャッシュ内の要素のライフタイムを管理するための非常に豊富な機能があり、MemoryCacheの実装が非常に便利になります。



このタイプのプロパティのほとんどは理解できるように見えますが、実際には、多くの予期しない驚きに遭遇します。 厳密に言えば、それらの多くはCacheItemPolicyクラス自体ではなく、このタイプを受け入れるMemoryCacheメソッドのロジックに含まれます。 しかし、これらのタイプはよく一緒に使用されるため、一緒に検討することを提案します。



AbsoluteExpirationおよびSlidingExpirationプロパティ


名前から、これらのプロパティが何を担当しているかは明らかです。 しかし、好奇心people盛な人は次の質問をするかもしれません:「両方のプロパティの値を同時に設定した場合、キャッシュはどのように動作しますか」 誰かがAbsoluteExpirationの方が優先度が高く、オブジェクトがAbsoluteExpirationの時点で、キャッシュから定期的に要求される場合でも(SlidingExpirationよりも頻繁に)オブジェクトが削除されることを示唆する場合があります。 それどころか、SlidingExpirationの値によってオブジェクトがAbsoluteExpirationを生き延びることができると想定する人がいます。 しかし、Microsoftの開発者は、真の正解はないと考え、異なる動作をしました-要素をキャッシュに追加する段階でArgumentExceptionをスローします。 したがって、各要素に対して1つの一時的(時間依存)障害戦略のみを選択できます。



キャッシュを使用する機能のテストを作成する場合、2番目の驚きが待っています。 確かに、テストの実行を高速化するために、SlidingExpirationにかなり小さな値(1秒未満)を設定します。 この場合、テストは不安定に動作し、しばしば失敗します。 これはすべて、要素(Getメソッドとその派生物)の読み取り時にキャッシュを最適化するため、新しいExpires値が古い値と少なくとも1秒異なる場合にのみ設定されるという事実によるものです。 これをドキュメントで確認することはできませんでしたが、MemoryCacheクラスを逆コンパイルし、内部クラスMemoryCacheEntryのUpdateSlidingExp(...)メソッドを調べることで確認できます。







優先プロパティ


このプロパティを見たとき、最大値に達したときにアイテムをキャッシュから削除する順序を設定するために、低/中/高の値があると予想しました。 ただし、CacheItemPriority.DefaultまたはCacheItemPriority.NotRemovableの2つの値しか持てません。

MSDNは、CacheItemPriority.NotRemovableを設定すると、アイテムがキャッシュから削除されなくなると述べています。 個人的に、私はこの事実を、そのような優先順位を持つすべての要素を追加することで、辞書のような実装を取得するという事実と考えましたが、これは事実とはほど遠いです。 要素は「フェード」すると(AbsoluteExpirationが発生するかSlidingExpirationがパスする)削除されますが、デフォルトモードとは異なり、占有メモリ量の制限に達してもメモリから削除されません。 ちなみに、制限はCacheMemoryLimitプロパティ(バイト単位)またはPhysicalMemoryLimitプロパティ(システム内のメモリの総量に対する割合)で設定できます。



RemovedCallbackおよびUpdateCallback


もう一つの驚き。 両方のプロパティはデリゲートによって受け入れられます。デリゲートは、更新の場合とキャッシュからアイテムを削除する場合の両方で呼び出されます。



考えてみると、更新は基本的に削除操作であり、その直後に新しい値を追加する操作が続きます。 これは、アイテムが更新されたときにRemovedCallbackが起動する理由を説明しています。 そして、UpdateCallbackが削除時にトリガーされるという事実は、MSDNからの事実にすぎません。



プロパティの違いは、RemovedCallbackを呼び出した後、UpdateCallback-キャッシュからアイテムを実際に削除する前に呼び出す必要があることです。 これらのプロパティに格納されているデリゲートは、キャッシュへのリンク、削除するアイテムへのリンク、およびアイテムを削除する理由を含むパラメーターを受け入れます。



別のギフトは、MemoryCacheの実装に保存されます。 このクラスには、CacheItemPolicyに渡されるパラメーターの少し奇妙な検証ロジックがあります。 最初に、両方のデリゲートが同時に設定されていないことを確認します。そうでない場合、キャッシュに要素を追加する段階でArgumentExceptionを取得します。







UpdateCallbackプロパティが正常に機能するためには、RemovedCallbackプロパティに値がないことを確認するだけで十分であれば、すべてうまくいきます。 しかし実際には、UpdateCallbackで空でない値を設定すると、要素を追加する段階で常にArgumentExceptionを取得します。





結果として、キャッシュ内の変更を通知するデリゲートを設定するための有効なプロパティはRemovedCallbackのみです(MemoryCacheの実装に対してのみ有効)。



ChangeMonitorsプロパティ


このプロパティは、ChangeMonitorタイプのオブジェクトのコレクションを格納できます。各オブジェクトは、アイテムがキャッシュから削除される条件を追加できます。



抽象ChangeMonitorクラスの独自の実装を作成できるという事実に加えて、次のクラスが.Net Frameworkに存在します。



CacheItemPolicyオブジェクトのこのプロパティは、アイテムをキャッシュに追加する前に設定する必要があることに注意してください。 既に追加されたアイテムに対して設定または変更しても効果はありません。



おわりに


MemoryCacheの実装にはあまり明らかではない機能が数多くありますが、このクラスは、要素無効化ポリシーを管理するための優れた機能を備えたスレッドセーフな「具体的な」作業キャッシュの実装を実現できるため、開発者の武器として非常に便利なツールです。 あなた自身のアナログを書く試みは時間がかかり、確かにそれほど効果的ではないと確信しています。



All Articles