web-dev-qa-db-ja.com

キーベースのキャッシュはどのように機能しますか?

私は最近 7Signalsブログ の記事を読み、キャッシュキーを取得するのはどうしてなのか疑問に思いました。

オブジェクトのタイムスタンプを含むキャッシュキーがあれば十分です(つまり、オブジェクトを更新すると、キャッシュが無効になります)。しかし、キャッシュからフェッチしようとしているオブジェクトそのものにDBヒットを引き起こさずに、テンプレートでキャッシュキーをどのように使用しますか。

具体的には、たとえば、投稿のコメントをレンダリングする1対多の関係にどのように影響しますか。

Djangoでの例:

{% for comment in post.comments.all %}
   {% cache comment.pk comment.modified %}
     <p>{{ post.body }}</p>
   {% endcache %}
{% endfor %}

Railsでのキャッシュは、たとえばmemcachedへのリクエストだけとは異なります(キャッシュキーを別のものに変換することは知っています)。キャッシュキーもキャッシュしますか?

10
Dominic Santos

既にロードされている単一のオブジェクトのストレートダンプをキャッシュする場合、はい、何も得られないか、またはほとんど得られません。それはそれらの例が説明しているものではありません-それらは階層を説明しているので、何かより低いものへの変更はまた、階層のより上のすべてへの更新をトリガーするはずです。

37signalsブログの最初の例では、_Project -> Todolist -> Todo_を階層として使用しています。入力された例は次のようになります。

_Project: Foo (last_modified: 2014-05-10)
   Todolist:  Bar1 (last_modified: 2014-05-10)
       Todo:  Bang1 (last_modified: 2014-05-09)
       Todo:  Bang2 (last_modified: 2014-05-09)

   Todolist:  Bar2 (last_modified: 2014-04-01)
       Todo:  Bang3 (last_modified: 2014-04-01)
       Todo:  Bang4 (last_modified: 2014-04-01)
_

したがって、_Bang3_が更新されたとしましょう。そのすべての親も更新されます:

_Project: Foo (last_modified: 2014-05-16)
   Todolist:  Bar2 (last_modified: 2014-05-16)
       Todo:  Bang3 (last_modified: 2014-05-16)
_

次に、レンダリングするときになると、データベースからのProjectのロードは基本的に避けられません。開始するにはポイントが必要です。ただし、その_last_modified_はそのすべての子のインジケーターであるため、子をロードする前にキャッシュキーとして使用します。


ブログの投稿では個別のテンプレートを使用していますが、私はそれらを1つにまとめます。 1つの場所で完全な対話を確認すると、少しわかりやすくなります。

したがって、Djangoテンプレートは次のようになります。

_{% cache 9999 project project.cache_key %}
<h2>{{ project.name }}<h2>
<div>
   {% for list in project.todolist.all %}
   {% cache 9999 todolist list.cache_key %}
      <ul>
         {% for todo in list.todos.all %}
            <li>{{ todo.body }}</li>
         {% endfor %}
      </ul>
   {% endcache %}
   {% endfor %}
</div>
{% endcache %}
_

_cache_key_がまだキャッシュに存在するプロジェクトを渡すとします。すべての関連オブジェクトへの変更を親に伝達するため、その特定のキーがまだ存在するということは、レンダリングされたコンテンツ全体がキャッシュからプルされることを意味します

その特定のプロジェクトが更新されたばかりである場合-たとえば、上記のFooのように-その子をレンダリングする必要があり、、その後のみそのプロジェクトのすべてのTodolistsに対してクエリを実行します。同様に、特定のTodolistの場合-そのリストのcache_keyが存在する場合、リスト内のTodoは変更されておらず、すべてをキャッシュから取得できます。

また、このテンプレートで_todo.cache_key_を使用していないことにも注意してください。質問で言ったように、bodyはすでにデータベースからプルされているので、価値はありません。ただし、何かをキャッシュする理由はデータベースのヒットだけではありません。たとえば、生のマークアップテキスト(StackExchangeの質問/回答ボックスに入力するものなど)を取得してHTMLに変換するには、結果をキャッシュするのに十分な時間がかかる場合があります。

その場合、テンプレートの内部ループは次のようになります。

_         {% for todo in list.todos.all %}
            {% cache 9999 todo todo.cache_key %}
               <li>{{ todo.body|expensive_markup_parser }}</li>
            {% endcache %}
         {% endfor %}
_

すべてをまとめるために、この回答の上部にある元のデータに戻りましょう。仮定すると:

  • すべてのオブジェクトは元の状態でキャッシュされました
  • _Bang3_が更新されました
  • 変更されたテンプレート(_expensive_markup_parser_を含む)をレンダリングしています

次に、これはすべてがロードされる方法です:

  • Fooはデータベースから取得されます
  • _Foo.cache_key_(2014-05-16)はキャッシュに存在しません
  • Foo.todolists.all()が照会されます:_Bar1_および_Bar2_がデータベースから取得されます
  • _Bar1.cache_key_(2014-05-10)すでにキャッシュに存在しています;それを取得して出力する
  • _Bar2.cache_key_(2014-05-16)はキャッシュに存在しません
  • Bar2.todos.all()が照会されます:_Bang3_および_Bang4_がデータベースから取得されます
  • _Bang3.cache_key_(2014-05-16)はキャッシュに存在しません
  • _{{ Bang3.body|expensive_markup_parser }}_がレンダリングされます
  • _Bang4.cache_key_(2014-04-01)すでにキャッシュに存在しています;それを取得して出力する

この小さな例でのキャッシュによる節約は次のとおりです。

  • データベースヒットを回避:Bar1.todos.all()
  • _expensive_markup_parser_を3回回避:_Bang1_、_Bang2_、および_Bang4_

そしてもちろん、次に表示したときに_Foo.cache_key_が見つかるので、レンダリングの唯一のコストは、データベースからFooだけを取得してキャッシュをクエリすることです。

3
Izkata