10

オプションの memcached キャッシングを既存のデータベース クエリに簡単にラップできます。例えば:

古い (DB のみ):

function getX
    x = get from db
    return x
end

新規 (memcache を使用する DB):

function getX
    x = get from memcache
    if found
      return x
    endif

    x = get from db
    set x in memcache
    return x
end

ただし、それは常にキャッシュしたい方法とは限りません。たとえば、次の 2 つのクエリを考えてみましょう。

-- get all items (recordset)
SELECT * FROM items;

-- get one item (record)
SELECT * FROM items WHERE pkid = 42;

上記の疑似コードを使用してキャッシュを処理すると、アイテム 42 のすべてのフィールドを 2 回格納することになります。ビッグレコードセットに1回、単独で1回。私はむしろこのようなことをしたいのですが:

SELECT pkid FROM items;

PK のそのインデックスをキャッシュします。次に、各レコードも個別にキャッシュします。

要約すると、DB に最適なデータ アクセス戦略は、memcache 戦略にはうまく適合しません。私は memcache レイヤーをオプションにしたいので (つまり、memcache がダウンしていても、サイトは引き続き機能します)、両方の長所を活用したいと考えていますが、そうするには、維持する必要があると確信しています。 2 つの異なる形式の多数のクエリ (1. インデックスを取得してからレコードを取得する、2. 1 つのクエリでレコードセットを取得する)。ページネーションでさらに複雑になります。DB では LIMIT/OFFSET SQL クエリを実行しますが、memcache では PK のインデックスを取得してから、関連する配列のスライスをバッチ取得します。

これをきちんと設計する方法がわからないのですが、何か提案はありますか?

あなたがこれに自分自身で立ち向かったのであればなおさらです。どのように処理しますか?

4

4 に答える 4

4

キャッシュを使用している場合、それを最大限に活用するには、データが常にある程度古くなり、データの一部が互いに同期されなくなることを受け入れる必要があります。単一のコピーを維持することによってすべてのレコードを最新の状態に維持しようとすることは、リレーショナル データベースに任せるのが最善です。したがって、これが必要な動作である場合は、多くの RAM を備えた強力な 64 ビット DB サーバーを使用する方がよいでしょう。そのため、独自の内部キャッシングを実行できます。

古いデータを受け入れることができる場合 (実際のスケーラビリティが重要な場合は受け入れる必要があります)、1 つの方法は、結果セット全体をキャッシュに入れることです。重複を心配しないでください。RAMは安いです。キャッシュがいっぱいになっていることがわかった場合は、RAM やキャッシュ サーバーを追加購入してください。たとえば、条件 X および Y によってフィルター処理されたセット内のアイテム 1 ~ 24 を表すクエリがある場合、このすべての情報を含むキャッシュ キーを使用し、同じ検索を再度要求されたときに、キャッシュ。1 回のヒットでキャッシュから完全な結果セットを取得するか、データベースにアクセスします。

最も難しいのは、どの程度のデータが古くなる可能性があるか、(a) 人々があまり気付かない、または (b) 最小更新間隔などのビジネス要件を破ることなく、データがどの程度古くなる可能性があるかを判断することです。

このアプローチは、読み取りがほとんどのアプリケーション、特にページングされたクエリおよび/またはデータのフィルター条件の有限セットを持つアプリケーションに適しています。また、アプリケーションはキャッシュがオンでもオフでもまったく同じように動作し、キャッシュがオフの場合はヒット率が 0% になることも意味します。これは、blinkBox がほぼすべての場合に採用しているアプローチです。

于 2008-11-10T02:01:42.473 に答える
3

Identity Mapパターンについて読んでください。これは、アプリケーション空間に特定の行のコピーを 1 つだけ保持するようにする方法です。memcached に格納する場合でも、単なるオブジェクトに格納する場合でも、これは必要なものを処理する方法です。通常、一度に 1 行ずつフェッチする場合は、Identity Map を使用するのが最適であると思います。

テーブルのサブセット全体をフェッチする場合、各行を個別に処理する必要があります。行の 99% がキャッシュにあるがデータベースからフェッチする必要がある場合、とにかく SQL クエリを実行する必要があるため (少なくとも一度)。

キャッシュにない行のみをフェッチするように SQL クエリを変換することもできますが、SQL クエリのコストを増やさずにこの変換を自動的に実行するのは簡単ではありません。

于 2008-11-10T01:38:55.873 に答える
1

まあ、それはあなたが一緒に暮らさなければならないものだと思います。Memcahcedは、実際にバッチで処理を行わない場合に最適に機能します。たとえば、「このユーザーにとってはどこにあるのか、このユーザーにとってはたくさんのことがある」などの場合に最適です。これは、このクエリがバッチを実行しないという意味ではありません。もちろん、それはそうなるでしょう-ユーザーのもののいくつかが彼/彼女の投稿のようなものであるならば。

あなたが抱える問題は、DBからアイテムを取得する必要のあるクエリと、同じ種類の前のアイテムの束を取得するクエリを混在させている場合だと思います。

状況には常に裏返しがあります。実装を本当にやりたい場合は、バッチクエリを変更して、memcachedにすでに存在するアイテムを含めないようにすることができます。非常に醜い...

私の意見では、それは常に「どのクエリを本当にキャッシュしたいのか」に帰着します。

編集:

私がこれについて行く方法は次のとおりです。

  • 単一アイテムクエリ-memcachedの場合はそれを使用し、そうでない場合はDBからフェッチしてmemcachedを更新します。
  • バッチクエリ-memcachedにあるアイテムについて心配する必要はありません。すべてを取得して、memcachedを更新するだけです。

もちろん、これは、バッチクエリの完了にすでにかなりの時間がかかることを前提としているため、すでにキャッシュされているアイテムへの外部ルックアップを使用して作業できるように、すでに多くの時間を費やしています。

ただし、バッチクエリを頻繁に使用すると、最終的にはキャッシュに多くのアイテムが含まれるようになります。したがって、データベースルックアップを実行するポイントを決定するためにバランスをとる必要があります。バッチクエリがアプリケーションのライフサイクルの早い段階にある場合は、すべてが早くキャッ​​シュされます。最初のバッチクエリの後、キャッシュ内のデータが更新または削除によって無効にされない限り、DBからフェッチする必要がなくなったことを自分自身に伝えることができます。

于 2008-11-10T01:22:21.490 に答える
1

これがNHibernate(したがっておそらくHibernate)がどのようにそれを行うかについての私の理解です。4 つのキャッシュがあります。

  • 行キャッシュ: DB 行をキャッシュします。キャッシュ キーは TableName#id で、その他のエントリは行の値です。
  • クエリ キャッシュ: これは、特定のクエリに対して返された結果をキャッシュします。キャッシュ キーはパラメーターを含むクエリで、データはクエリ結果として返された TableName#id 行キーのリストです。
  • コレクション キャッシュ: これは任意の親の子オブジェクトをキャッシュします (NHibernate では遅延読み込みが可能です)。 したがって、myCompany.Employees にアクセスすると、 employees コレクションがコレクション キャッシュにキャッシュされます。キャッシュ キーは CollectionName#entityId で、データは子行の TableName#id 行キーのリストです。
  • テーブル更新キャッシュ: 各テーブルのリストと、最後に更新された日時。データがキャッシュされた後にテーブルが更新された場合、データは古いと見なされます。

これは非常に柔軟なソリューションであり、スペースに関して非常に効率的であり、データが古くならないことが保証されます。欠点は、1 つのクエリでキャッシュへのラウンドトリップが数回必要になる可能性があることです。これは、キャッシュ サーバーがネットワーク上にある場合に問題になる可能性があります。

于 2010-08-24T13:16:09.810 に答える