キャッシュの無効化—一般的な解決策はありますか?


118

「コンピューターサイエンスには、キャッシュの無効化と名前の付け方という2つの難しい問題しかありません。」

フィル・カールトン

キャッシュを無効にする一般的な解決策または方法はありますか?エントリが古くなったことを知るために、常に最新のデータを取得することが保証されていますか?

たとえばgetData()、ファイルからデータを取得する関数について考えます。ファイルが最後に変更された時刻に基づいてキャッシュされ、呼び出されるたびにチェックされます。
次にtransformData()、データを変換する2番目の関数を追加し、その関数が次に呼び出されるときにその結果をキャッシュします。それはファイルの知識を持っていません-ファイルが変更された場合、このキャッシュが無効になるという依存関係をどのように追加しますか?

が呼び出さgetData()れるたびに呼び出しtransformData()、それをキャッシュの構築に使用された値と比較できますが、結果として非常にコストがかかる可能性があります。


6
彼はXウィンドウの作成と関係があると思います
グレッグ

1
タイトルは「キャッシュの無効化-一般的な解決策はありますか?」特定のクラスのキャッシング問題を指すため。
RBarryYoung 2009

71
いいえ、彼はコンピュータサイエンスについてはあまり知りませんでした。彼がOpenGL、X11、およびSSLv3の作成に携わったことで、彼は忙しくなり、実際にそれを詳しく研究できなくなったと思います。:-)
Tim Lesher、

80
コンピュータサイエンスには2つの難しい問題しかありません。キャッシュの無効化です。ものに名前を付ける。そして、1つずれたエラー。
Dag

8
私はかつてこれを聞いたことがあります"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Jonathon Reinhart 2014年

回答:


55

あなたが話しているのは、生涯の依存関係の連鎖であり、あるものは他のものに依存しており、その制御外で変更することができます。

あなたがからの冪等の機能がある場合はabc、場合ab同じですその後、c同じですが、チェックのコストがb高いか、あなたです。

  1. あなたは時々古い情報で操作し、常にチェックしないことを受け入れる b
  2. bできる限り迅速にチェックできるようにレベルを最善にしてください

ケーキを持って食べられない...

上に基づいて追加のキャッシュを階層化できる場合a、これは1ビットではなく初期の問題に影響します。1を選択した場合は、自由を与えてキャッシュを増やすことができますが、のキャッシュされた値の有効性を考慮することを忘れないでくださいb。2を選択した場合でも、b毎回チェックする必要がありますがabチェックアウトする場合はキャッシュにフォールバックできます。

キャッシュを階層化する場合、組み合わせた動作の結果としてシステムの「ルール」に違反していないかどうかを考慮する必要があります。

a常に有効であることがわかっている場合bは、キャッシュをそのように配置できます(疑似コード)。

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;
}

x各ステージで新しく追加された入力の有効性が:と:のab関係に一致する限り、明らかに連続した階層化(たとえば)は簡単です。xbxa

ただし、有効性が完全に独立している(または循環的であった)3つの入力を取得できる可能性があるため、階層化は不可能です。これは、//重要とマークされた行を次のように変更する必要があることを意味します

if(endCache [a] 期限切れまたは存在しない)


3
あるいは、bをチェックするコストが高い場合は、pubsubを使用して、bが変更されたときにcに通知するようにします。Observerパターンは一般的です。
user1031420 2014年

15

キャッシュの無効化の問題は、私たちが知らないうちに内容が変更されることです。したがって、場合によっては、それについて知っていて通知できる他のことがあれば、解決策が考えられます。与えられた例では、getData関数はファイルシステムにフックし、どのプロセスがファイルを変更したかに関係なく、ファイルへのすべての変更を認識します。このコンポーネントは、データを変換するコンポーネントに通知します。

問題を解消するための一般的な魔法の修正はないと思います。しかし、多くの実際的なケースでは、「ポーリング」ベースのアプローチを「割り込み」ベースのアプローチに変換する機会が非常にあり、問題を単純に解消することができます。


3

変換を行うたびにgetData()を実行する場合は、キャッシュのメリットをすべて排除できます。

あなたの例では、変換されたデータを生成するときに、データが生成されたファイルのファイル名と最終変更時刻も格納するための解決策のようです(getData( )、transformData())によって返されたデータ構造にそのレコードをコピーしてから、transformData()を再度呼び出すときに、ファイルの最終変更時刻を確認します。


3

IMHO、Functional Reactive Programming(FRP)は、キャッシュの無効化を解決する一般的な方法です。

理由は次のとおりです。FRP用語で古いデータはグリッチと呼ばれます。FRPの目標の1つは、グリッチがないことを保証することです。

FRPについては、この「Essence of FRP」トークとこのSO回答で詳しく説明しています

ではCell Sは、キャッシュされたオブジェクト/エンティティを表し、Cellそれの依存関係のいずれかが更新された場合に更新されます。

FRPは、依存関係グラフに関連付けられた配管コードを非表示にし、古いCells がないことを確認します。


私が考えることができる別の方法(FRPとは異なります)は、計算された値が依存する変更可能な値のすべての識別子を(Haskell表記)が含むb、ある種のライターモナドWriter (Set (uuid)) bSet (uuid)(タイプ)の計算値をラップすることですb。したがって、uuid計算がb依存する変更可能な値/変数(データベースの行など)を識別する、ある種の一意の識別子です。

このアイデアを、この種のライターモナドで動作するコンビネータと組み合わせると、これらのコンビネータを使用して新しいを計算するだけの場合、ある種の一般的なキャッシュ無効化ソリューションにつながる可能性がありますb。このようなコンビネータ(たとえば、特別なバージョンのfilter)は、Writerモナドと(uuid, a)-sを入力として使用auuidます。ここで、は、で識別される変更可能なデータ/変数です。

あなたは「オリジナル」のデータを変更するたびだから、(uuid, a)(そこから、データベースの正規化されたデータと言うbタイプの計算値がどの計算された)をb、あなたが含まれているキャッシュを無効にすることができます依存しbますが、任意の値突然変異した場合a、計算されているb値が依存は、Set (uuid)Writerモナドに基づいているので、これがいつ発生するかを知ることができます。

与えられたとだから、いつでもあなたのmutate何かuuid、あなたはすべてのキャッシュ-Sにこの変異をブロードキャストし、彼らが値を無効bとして識別される可変値に依存uuidするライターモナドので、bそれがあれば言うことができる包まれbたに依存しuuidたりしますない。

もちろん、これはあなたが書くよりもはるかに頻繁に読む場合にのみ効果があります。


3番目の実用的なアプローチは、データベースでマテリアライズドビューを使用し、それらをキャッシュとして使用することです。私の知る限り、彼らはまた、無効化の問題を解決することを目指しています。もちろんこれは、可変データを派生データに接続する操作を制限します。


2

私は現在、PostSharpメモ機能をベースにしたアプローチに取り組んでいます。私はメンターを超えてそれを実行しました、そして彼はそれがコンテンツにとらわれない方法でのキャッシングの良い実装であることに同意します。

すべての関数は、その有効期限を指定する属性でマークすることができます。このようにマークされた各関数はメモ化され、結果はキャッシュに保存されます。関数呼び出しのハッシュとパラメーターがキーとして使用されます。キャッシュデータの配布を処理するバックエンドにVelocityを使用しています。


1

エントリが古くなっていることを知るためにキャッシュを作成する一般的な解決策または方法はありますか?そのため、常に最新のデータを取得することが保証されていますか?

いいえ、すべてのデータが異なるためです。1分後、1時間後、「数日または数か月間は問題ない」データもあります。

あなたの具体的な例については、最も簡単な解決策は、あなたが、両方から呼び出すファイルのための「キャッシュのチェック」機能を持つことであるgetDatatransformData


1

一般的な解決策はありませんが、

  • キャッシュはプロキシーとして機能できます(プル)。キャッシュが最後の起点変更のタイムスタンプを知っていると想定します。誰かがを呼び出すgetData()と、キャッシュは起点に最後の変更のタイムスタンプを要求し、同じ場合はキャッシュを返します。それ以外の場合は、ソースのコンテンツでコンテンツを更新してそのコンテンツを返します。(バリエーションは、リクエストのタイムスタンプを直接送信するクライアントです。ソースは、タイムスタンプが異なる場合にのみコンテンツを返します。)

  • 引き続き通知プロセス(プッシュ)を使用できます。キャッシュはソースを監視します。ソースが変更された場合、キャッシュに通知が送信され、「ダーティ」としてフラグが立てられます。誰かがgetData()キャッシュを呼び出して最初にソースが更新される場合は、「ダーティ」フラグを削除します。その後、そのコンテンツを返します。

一般的に言えば、選択は以下に依存します。

  • 頻度:多くの呼び出しでgetData()はプッシュが優先されるため、getTimestamp関数によってソースがフラッディングされるのを回避します。
  • ソースへのアクセス:ソースモデルを所有していますか?そうでない場合は、通知プロセスを追加できない可能性があります。

注:タイムスタンプの使用はhttpプロキシが機能する従来の方法であるため、別のアプローチは、格納されたコンテンツのハッシュを共有することです。2つのエンティティが一緒に更新されることを私が知る唯一の方法は、私があなたに電話をかける(プルする)か、私に電話する(プッシュ)ことです。



-2

おそらく、キャッシュを気にしないアルゴリズムが最も一般的(または、少なくとも、ハードウェア構成に依存しない)になるでしょう。これは、最初に最速のキャッシュを使用し、そこから先に進むためです。MITでの講義は次のとおりです。キャッシュ忘却アルゴリズム


3
私は彼がハードウェアキャッシュについて話しているのではないと思います-彼はgetData()コードがファイルから取得したデータをメモリに「キャッシュする」機能を持っていることについて話していると思います。
Alex319
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.