「コンピューターサイエンスには、キャッシュの無効化と名前付けという2つの困難な問題しかありません。」
フィルカールトン
キャッシュを無効にする一般的な解決策または方法はありますか?エントリがいつ古くなったかを知るために、常に新しいデータを取得することが保証されますか?
たとえば、ファイルからデータを取得する関数getData()
を考えます。ファイルの最終変更時刻に基づいてキャッシュされ、呼び出されるたびにチェックされます。
次に、2番目の関数transformData()
を追加します。この関数は、データを変換し、その関数が次回呼び出されるときにその結果をキャッシュします。ファイルの知識がありません-ファイルが変更された場合、このキャッシュが無効になるという依存関係をどのように追加しますか?
getData()
が呼び出されるたびにtransformData()
を呼び出して、キャッシュの作成に使用された値と比較できますが、非常にコストがかかる可能性があります。
あなたが話しているのは、ライフタイムの依存関係の連鎖です。つまり、あるものは別のものに依存しており、その制御外で変更することができます。
a
、b
からc
までのべき等関数がある場合、a
とb
が同じ場合、c
は同じです。ただし、b
をチェックするコストが高い場合は、次のいずれかを行います。
b
をチェックするとは限らないことを受け入れるb
を可能な限り高速にチェックするために最善を尽くしますケーキを持って食べることはできません...
a
に基づいて追加のキャッシュを上に重ねることができる場合、これは1ビットではなく最初の問題に影響します。 1を選択した場合、自由に自由に設定できるため、キャッシュを増やすことができますが、b
のキャッシュ値の有効性を考慮する必要があります。 2を選択した場合、b
を毎回チェックする必要がありますが、a
がチェックアウトする場合、b
のキャッシュにフォールバックできます。
キャッシュを階層化する場合は、組み合わせた動作の結果としてシステムの「ルール」に違反しているかどうかを考慮する必要があります。
a
がb
である場合、x
が常に有効であることを知っている場合、次のようにキャッシュを調整できます(擬似コード)。
private map<b,map<a,c>> cache //
private func realFunction // (a,b) -> c
get(a, b)
{
c result;
map<a,c> endCache;
if (cache[b] expired or not present)
{
remove all b -> * entries in cache;
endCache = new map<a,c>();
add to cache b -> endCache;
}
else
{
endCache = cache[b];
}
if (endCache[a] not present) // important line
{
result = realFunction(a,b);
endCache[a] = result;
}
else
{
result = endCache[a];
}
return result;
}
明らかに、次の階層化(たとえばa
)は、各段階で、新しく追加された入力の有効性がb
:_ [のx
:b
関係と一致する限り、簡単です。 VARIABLE] _およびx
:a
。
ただし、妥当性が完全に独立した(または周期的な)3つの入力を取得できる可能性が非常に高いため、階層化はできません。これは、//重要とマークされた行を次のように変更する必要があることを意味します
if(endCache [a] expired or not present)
キャッシュの無効化の問題は、知らないうちに内容が変わることです。そのため、場合によっては、それについて知っていて通知できる他のことがあれば、解決策が可能です。与えられた例では、getData関数は、ファイルを変更するプロセスに関係なく、ファイルへのすべての変更について知っているファイルシステムにフックでき、このコンポーネントはデータを変換するコンポーネントに通知できます。
問題を解決するための一般的な魔法の修正はないと思います。しかし、多くの実際のケースでは、「ポーリング」ベースのアプローチを「割り込み」ベースのアプローチに変換する機会が非常によくあり、それによって問題が単純になくなる可能性があります。
変換を行うたびにgetData()を実行する場合は、キャッシュの利点をすべて排除しています。
あなたの例では、変換されたデータを生成するとき、データが生成されたファイルのファイル名と最終変更時刻も保存するためのソリューションのようです(getData( )、そのレコードをtransformData())によって返されるデータ構造にコピーし、transformData()を再度呼び出すときに、ファイルの最終変更時刻を確認します。
PostSharp と memoizing functions に基づいたアプローチに取り組んでいます。私はメンターを過ぎて実行しましたが、コンテンツに依存しない方法でのキャッシュの優れた実装であることに彼は同意します。
すべての関数は、有効期限を指定する属性でマークできます。この方法でマークされた各関数はメモされ、結果はキャッシュに保存され、関数呼び出しのハッシュとパラメーターがキーとして使用されます。キャッシュデータの配布を処理する Velocity をバックエンドに使用しています。
キャッシュを作成する一般的な解決策や方法はありますか?エントリが古くなったときに知るため、常に新しいデータを取得することが保証されていますか?
いいえ、すべてのデータが異なるためです。一部のデータは1分後に「古い」場合もあれば、1時間後に「古い」場合もあれば、数日または数か月間は問題ない場合もあります。
あなたの特定の例に関して、最も簡単な解決策は、getData
とtransformData
の両方から呼び出すファイルの「キャッシュチェック」機能を持つことです。
一般的な解決策はありませんが:
キャッシュはプロキシ(プル)として機能できます。キャッシュが最後のOrigin変更のタイムスタンプを知っていると仮定します。誰かがgetData()
を呼び出すと、キャッシュはOriginに最後の変更のタイムスタンプを要求します。同じ場合、キャッシュを返します。その内容を返します。 (バリエーションは、リクエストでタイムスタンプを直接送信するクライアントです。ソースは、タイムスタンプが異なる場合にのみコンテンツを返します。)
通知プロセス(プッシュ)を引き続き使用できます。キャッシュはソースを監視し、ソースが変更された場合、キャッシュに通知を送信し、キャッシュに「ダーティ」のフラグを立てます。誰かがgetData()
を呼び出した場合、キャッシュは最初にソースに更新され、「ダーティ」フラグを削除します。その後、そのコンテンツを返します。
一般的に言えば、選択は以下に依存します。
getData()
に対する多くの呼び出しは、ソースがgetTimestamp関数によってフラッディングされるのを避けるために、Pushを好むでしょう。注:タイムスタンプの使用はhttpプロキシが機能する従来の方法であるため、別のアプローチは保存されたコンテンツのハッシュを共有することです。 2つのエンティティが一緒に更新されることを知っている唯一の方法は、私があなたを呼び出す(プル)か、あなたが私を呼び出す...(プッシュ)のいずれかです。
1)キャッシュが複数のノードであり、それらのコンセンサスが必要である2)無効化時間3)複数のget/setが発生した場合の競合状態
これは良い読書です: https://www.confluent.io/blog/turning-the-database-inside-out-with-Apache-samza/