2

Memcachedは優れたスケーラブルなキャッシュレイヤーですが、タグを管理できないという大きな問題が1つあります(私にとって)。また、タグはグループの無効化に非常に役立ちます。

私はいくつかの調査を行い、いくつかの解決策について知っています。

私のお気に入りのソリューションの1つは名前空間であり、このソリューションはmemcachedwikiで説明されています。

しかし、なぜ名前空間をキーキャッシュに統合するのかわかりませんか?

名前空間のトリックについて私が理解したことから、キーを生成するには、(キャッシュ上の)名前空間の値を取得する必要があります。そして、namespace->valueキャッシュエントリが削除されると、キャッシュをフェッチするための適切なキーを計算できなくなります...したがって、この名前空間のキャッシュは事実上無効になります(キャッシュがまだ存在するため、事実上言ったが、アクセスするためのキーを計算することはできません) )。

では、なぜ次のようなものを単純に実装できないのでしょうか。

tag1->[key1, key2, key5]
tag2->[key1, key3, key6]
key1->["value" => value1, "tags" => [tag1, tag2]]
key2->["value" => value2, "tags" => [tag1]]
key3->["value" => value3, "tags" => [tag3]]
etc...

この実装では、削除された場合、tag1->[key1, key2, key5]tag1キーを無効にできないという問題が発生します。しかし、

function load($cacheId) {
   $cache = $memcache->get($cacheId);
   if (is_array($cache)) {
      $evicted = false;
      // Check is no tags have been evicted
      foreach ($cache["tags"] as $tagId) {
         if (!$memcache->get($tagId) {
            $evicted = true;
            break;
         }
      }
      // If no tags have been evicted we can return cache
      if (!$evicted) {
         return $cache
      } else {
         // Not mandatory
         $memcache->delete($cacheId);
      }
      // Else return false
      return false;
   }
}

擬似コードです

このタグがすべて使用可能な場合は、必ずキャッシュを返します。

そして、最初に言えることは、「キャッシュを取得する必要があるたびに、Xタグをチェック(/取得)してから、配列をチェックする必要がある」ということです。しかし、名前空間では、名前空間の値を取得するために名前空間をチェック(/ get)する必要があります。主な違いは、配列の下で反復することです...しかし、キーに多くのタグがあるとは思いません(10個を超えるタグ/キーを想像することはできません)私のアプリケーションの場合)、サイズ10の配列の下で反復するので、かなり高速です。

だから私の質問は:誰かがすでにこの実装について考えていますか?そして、限界は何ですか?何か忘れましたか?等

あるいは、名前空間の概念を誤解しているかもしれません...

PS:memcached-tagやredisのような別のキャッシュレイヤーを探していません

4

1 に答える 1

1

この実装で何かを忘れていると思いますが、修正するのは簡単です。

複数のキーがいくつかのタグを共有する問題を考えてみましょう。

key1 -> tag1 tag2
key2 -> tag1 tag2
tag1 -> key1 key2
tag2 -> key1 key2

key1をロードするとします。tag1とtag2の両方が存在することを再確認します。これは問題なく、キーがロードされます。

次に、tag1は何らかの形でキャッシュから削除されます。

次に、コードはtag1を無効にします。これにより、key1とkey2が削除されますが、tag1が削除されているため、これは発生しません。

次に、新しいアイテムkey3を追加します。tag1も参照します。

key3 -> tag1

このキーを保存すると、tag1が(再)作成されます。

tag1 -> key3

後で、キャッシュからkey1を再度ロードするときに、tag1が存在することを確認するための擬似コードのチェックが成功します。そして、key1からの(古い)データをロードすることが許可されます。

明らかに、これを回避する方法は、tag1データの値をチェックして、ロードしているキーがその配列にリストされていることを確認し、これがtrueの場合にのみキーが有効であると見なすことです。

もちろん、ユースケースによってはパフォーマンスの問題が発生する可能性があります。特定のキーに10個のタグがあり、それらのタグのそれぞれが10k個のキーによって使用されている場合、キーを見つけるために10k個のアイテムの配列を検索し、何かをロードするたびにそれを10回繰り返す必要があります。

ある時点で、これは非効率になる可能性があります。

読み取りと書き込みの比率が非常に高い場合は、別の実装(および私が使用する実装)の方が適しています。

読み取りが非常に一般的なケースである場合は、タグ機能をより永続的なデータベースバックエンドに実装できます(とにかく一種のデータベースがあると想定しているので、ここでは2、3の追加テーブルのみが必要です)。

キャッシュにアイテムを書き込むときは、キーとタグを単純なテーブルに格納します(キーとタグの列、キーのタグごとに1行)。キーの記述は簡単です。

タグを無効にするときは、そのタグを持つすべてのキーを繰り返し処理し(tag =:tag;の場合はcache_tagsからキーを選択します)、それぞれを無効にします(オプションで、cache_tagsテーブルからキーを削除して整理します)。

キーがmemcacheから削除された場合、cache_tagsメタデータは古くなりますが、これは通常無害です。タグを無効にしようとすると、そのタグが含まれているがすでに削除されているキーを無効にしようとすると、せいぜい非効率になります。

このアプローチでは、「無料」の読み込み(タグをチェックする必要はありません)が得られますが、コストのかかる節約になります(とにかくすでにコストがかかります。そうでなければ、そもそもキャッシュする必要はありません!)。

したがって、ユースケースと予想される負荷パターンおよび使用法に応じて、元の戦略(負荷をより厳密にチェックする)または「データベースに裏打ちされたタグ」戦略のいずれかがニーズに適合することを願っています。

HTH

于 2012-06-19T10:51:32.100 に答える