Django Cachingの詳細

キャッシングとは何か、なぜキャッシングが必要なのかは誰もが知っています。 出席者が増え、データベースの負荷が増加しているため、キャッシュからデータを送信することにしました。 理想的な世界では、おそらくこのためには、settings.pyにUSE_CACHE = Trueという行を追加するだけで十分ですが、この時間が来るまでには、もう少し体の動きが必要になります。

Djangoでキャッシュを使用する場合は、選択を行う必要があります。舞台裏ですべてを実行する既製のソリューションを使用するか、独自のソリューションを実装します。 他の多くの状況とは異なり、既存の既製のソリューションには多くの制限と潜在的な不都合があるため、この選択はここではそれほど明白ではありません。

最初に、既製のソリューションをすばやく調べ、次に、独自にキャッシングを実装する最適な方法を見つけます。

既製のソリューション


djangopackages.comにアクセスして 、何私たちを幸せにする確認します

ジョニー・キャッシュ


すべてのORM要求が自動的にキャッシュされるようにDjangoクエリセットをキャッチします。 インストールは可能な限り簡単です。settings.pyの新しい行が2つあります。それ以外はすべて同じで、クエリ構文は変わりません。 データは永久にキャッシュされ、変更時に無効になります。

しかし、単に障害があると、ジョニーの全体的な効果が失われる可能性があります。 データベース全体のテーブルは無効です。 つまり、9000人のユーザーがいる場合、少なくとも1人のユーザーを変更すると、すべてのユーザーのキャッシュがリセットされます。 まれにしか変更されないテーブルをキャッシュする必要がある場合、このソリューションは、他のケース(およびそれらのほとんど)で機能する可能性があります。

Djangoキャッシュマシン


ORMリクエストもキャッシュされます。 .get()のみがキャッシュされ、 .get()リクエストはキャッシュされません。 .values()および.values_list().values()しません。 使用するには、mixinとmanagerをモデルに追加する必要があります。

障害に合理的にアプローチしようとしています。 1つのオブジェクトを変更すると、このオブジェクトが含まれるキャッシュ要素のみが無効になります( ForeignKeyManyToManyなどの関係を考慮に入れて)。

Django-cachebot



すべての.get()要求を自動的にキャッシュします。 クエリセットをキャッシュするには、 cache()メソッドを呼び出す必要があります。 彼のマネージャーを使用します。

無効化は、Django Cache Machineとほぼ同じです。 ManyToMany変更しても無効になりません。

合計


Django Cache MachineとDjango-cachebotで問題を解決できます。JohnnyCacheは障害に無差別すぎるので、お勧めしません。

持って使用できるように思えますが、覚えておく必要のあることがいくつかあります。

そのようなことがわからない場合は、ターンキーソリューションが最適です。 何らかの理由で、自分でキャッシングを実装する場合は、次に進みます。

自分でやる


アーキテクチャ的には、2つのことを実装する必要があります。 最初の1つはデータを受信するロジックです。データをキャッシュに入れるかどうかを調べ、データがある場合はデータベースから取り出し、キャッシュに入れて返します。 2つ目は障害の論理です。

データを取得します


ここではすべてがシンプルで明白です。

  cached = cache.get('my_key') if cached is not None: return cached result = make_heavy_query() cache.set('my_key', result) return result 


Q :データを永久に保存する方法(無限タイムアウト) A :これをサポートするバックエンドを使用します(例: django-newcache)

Q :キャッシュにNoneを保存したい場合はどうなりますか? Aドキュメントを読んで、 None代わりに任意の値を使用できることを見つけてください。

  cached = cache.get('my_key', -1) if cached != -1: # ... 


キャッシュに関連付けられたコードをどこに保存しますか?


主なことは、プロジェクト全体に広げることではなく、1か所に保管することです。 私の(そして私だけの)意見では、最も適切な場所は対応するモデルのマネージャーです。 MyModel.objectsをオーバーライドして、別のタイプのMyModel.cached追加できます。

多くの場合、関連オブジェクトへのアクセスをキャッシュするコードが必要です。 たとえば、一部の記事では、タグのリストを取得する必要があります。 モデルメソッドにキャッシングコードを配置する誘惑がありますが、私は一貫性を保ち、マネージャーを介してこれを行うことに賛成です。 そして、すでにモデルで、マネージャーに連絡してください:

  class Article(models.Model): # ... def get_tags(self): return Article.cached.tags_for_instance(self.id) 


データを保存する方法は?


インスタンスモデルはそのようにキャッシュでき、完全にシリアル化できます。 get_FOO_displayなどのすべてのメソッドが機能しget_FOO_display 。 関連オブジェクト( ForeignKeyManyToMany )がキャッシュに入らないことを覚えておく必要があるだけで、それらにアクセスしようとすると、ベースが再びManyToManyます。 したがって、それらにアクセスするための独自のメソッドを追加することをお勧めします(上記の例を参照)。

クエリセットをキャッシュする必要がある場合は、最初にリストに追加することをお勧めします。 キャッシュすることもできますが、これはDjangoのバージョン間の互換性の問題の影響を受ける可能性があります

オブジェクトのリストが比較的小さく、都市や学部などのリストがめったに変更されず、要素の順序が重要でない場合は、次の形式で辞書の形式で保存できます。
 dict((x.id, x) for x in MyModel.objects.all()) 

これにより、キャッシュ内の1つのエントリで取得でき、オブジェクトごとにエントリを作成することはできません。

オブジェクトのリストではなく、識別子のリストだけを保存し、オブジェクト自体をget_manyして別のget_manyリクエストでキャッシュを取得することが理にかなっている場合があります。 さらに、無効化は、リストアイテムの構成が変更された場合、つまり頻度が低い場合にのみ必要です。 少ない:プラスのメリットがない場合があります。 ここでは、おそらく、例が必要です。 「最近の記事10件」のリストがあるとします。 IDのみを保存する場合、新しい記事がサイトに追加されるか、このリストから一部の記事が削除される場合にのみ、このリストを無効にする必要があります。 オブジェクトのリスト全体を保持する場合は、記事の変更時に無効化する必要があります(たとえば、タイプミスが修正されました)。 一方、記事があまり頻繁に追加されない場合、ここでの利点はないため、この方法はどこでも機能しません。

キーに名前を付ける方法は?


記事のリストなど、サイトに固有の何かを保存する場合、キーに好きな名前を付けることができます。 たとえば、 'articles' 。 毎回、 一度だけ一意のプレフィックスを追加する必要はありません。

キー名がオブジェクトに依存している場合は、文字列フォーマットを使用する必要があります。 多くの場合、これを行います: 'article::%d' 。 すべて問題ありませんが、より良いのは'article::%(id)d'です。 前者の場合、「ある種の全体」、後者の場合-アイデンティティ。 または、 'tags_for::%d''tags_for::%(article_id)d'比較し'tags_for::%(article_id)d' 。 この構文が奇妙に思える場合、 これは修正可能です。

障害者


障害は信号で最もよく行われます。 シグナルコードはどこにでも保存できます。これには@staticmethodのモデルクラスを使用します。 多くの場合、障害はあまり効果的ではありません。 典型的な例を次に示します。

  @receiver(post_save, sender=Article) @receiver(pre_delete, sender=Article) def invalidate(instance, **kwargs): cache.delete('article::%(id)d' % {'id': instance.id}) 


結局のところ、あなたは良くなることができます!

  @receiver(post_save, sender=Article) def on_change(instance, **kwargs): cache.set('article::%(id)d' % {'id': instance.id}, instance) @receiver(pre_delete, sender=Article) def on_delete(instance, **kwargs): cache.delete('article::%(id)d' % {'id': instance.id}) 


新しい値に置き換えることができるのに、なぜ値を削除するのですか? クエリをデータベースに保存し、ドッグパイル効果を防ぎます。 もちろん、オブジェクトの変更と削除の2つのハンドラーが必要になります。 できる限りこれを行う方が良いです。

障害ManyToMany


ManyToManyFieldは、追加のManyToManyField掛ける必要があります。 このようなもの:

  @receiver(m2m_changed, sender=Article.tags.through) def on_tags_changed(instance, **kwargs): # do update / invalidation 


ModelChoiceFieldおよびModelMultipleChoiceFieldのキャッシュ


Djangoには、これらのフィールドのオプションをキャッシュする組み込み機能がありません。 したがって、このフィールドをレンダリングするたびに、データベースへのリクエストが発生します。 手動でChoiceFieldMultipleChoiceFieldそれぞれ置き換える(+ロジックを追加する)か、私の小さなアプリケーションを使用できます 。 Django 1.2-1.4で正常に動作します。 ここでは十字架につけません。すべてがリンクに記載されています。

キャッシュの永続性に依存しないでください!


最後に、持続性について少し説明します。 第一に、この単語は非常に巧妙に見え、第二に、キャッシュを使用して正確に2つのことを実行できることを思い出してください。そこから読み取り、そこに新しい意味を書きます。 次のように、キャッシュ内のデータを変更しないでください。

  mylist = cache.get('mylist') mylist.append(value) cache.set('mylist', mylist) 


この操作はアトミックではありません。つまり、2つのクライアントが同時にリストを変更しないという保証はありません。 そして、これが起こると、何が問題なのか、間違ったデータを持っている理由を把握するために、眠れぬ夜を過ごすことになります。 しない方がいいです もちろん、アトミック性がバックエンドによって保証されている操作を使用できます。たとえば、 cache.incr() / cache.decr()です。

おわりに


上記の何かが最適でもエラーでもない場合は、コメントを書いて、記事を修正します。 彼女はもっと便利になり、読者はもっと幸せになります。 ありがとう

Source: https://habr.com/ru/post/J143789/


All Articles