コンテキスト
Djangoキャッシュマシンで、Django 1.4から1.7にアップグレードした後、無効化ロジックが気にならなくなるという、かなり重大なバグを見つけました。
このバグは、キャッシュマシンのCachingMixin
を拡張するモデルでのonly()
の呼び出しに限定されています。その結果、スタックが破壊されることがある深い再帰が発生しますが、それ以外の場合は、キャッシュマシンがForeignKey
関係のモデルの双方向無効化に使用する巨大なflush_lists
が作成されます。
class MyModel(CachingMixin):
id = models.CharField(max_length=50, blank=True)
nickname = models.CharField(max_length=50, blank=True)
favorite_color = models.CharField(max_length=50, blank=True)
content_owner = models.ForeignKey(OtherModel)
m = MyModel.objects.only('id').all()
バグ
バグは次の行で発生します( https://github.com/jbalogh/Django-cache-machine/blob/f827f05b195ad3fc1b0111131669471d843d631f/caching/base.py#L253-L254 )。この場合、self
はMyModel
のインスタンスであり、遅延属性と非遅延属性が混在しています。
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if isinstance(f, models.ForeignKey))
キャッシュマシンは、ForeignKey
関係全体で双方向の無効化を行います。これは、Model
内のすべてのフィールドをループし、問題のオブジェクトが無効化されたときに無効化が必要なオブジェクトを指す一連のポインターをキャッシュに格納することによって行われます。
Django ORMでonly()
を使用すると、DjangoのDeferredAttribute
実装でフェッチされていない属性をオーバーライドするメタプログラミングの魔法が実行されます。通常の状況ではfavorite_color
へのアクセスDeferredAttribute.__get__
( https://github.com/Django/django/blob/18f3e79b13947de0bda7c985916d5a04e28936dc/Django/db/models/query_utils.py#L121-L146 )を呼び出し、結果キャッシュから属性をフェッチしますまたはデータソース。これは、問題のModel
の遅延されていない表現をフェッチし、その上で別のonly()
クエリを呼び出すことによって行われます。
これは、Model
の外部キーをループしてそれらの値にアクセスするときの問題であり、CachineMachineは意図しない再帰を導入します。延期された属性のgetattr(self, f.attname)
は、Model
が適用され、延期された属性を持つCachingMixin
のフェッチを誘導します。これにより、キャッシュプロセス全体が最初からやり直されます。
質問
これを修正するためにPRを開きたいのですが、これに対する答えは遅延属性をスキップするのと同じくらい簡単だと思いますが、属性にアクセスするとフェッチプロセスが開始されるため、その方法がわかりません。
私が持っているのが、遅延属性と非遅延属性が混在するModel
のインスタンスのハンドルだけである場合、属性がDeferredAttribute
であるかどうかを判断する方法はありますかアクセスせずに?
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and <f's value isn't a Deferred attribute))
フィールドが延期されているかどうかを確認する方法は次のとおりです。
from Django.db.models.query_utils import DeferredAttribute
is_deferred = isinstance(model_instance.__class__.__dict__.get(field.attname), DeferredAttribute):
取得元: https://github.com/Django/django/blob/1.9.4/Django/db/models/base.py#L39
これにより、属性が遅延属性であり、がデータベースからまだロードされていないかどうかがチェックされます。
fks = dict((f, getattr(self, f.attname)) for f in self._meta.fields
if (isinstance(f, models.ForeignKey) and f.attname in self.__dict__))
内部的には、type(self)
は 新しく作成された 元のクラスのプロキシモデルです。 DeferredAttribute
は、最初にインスタンスの local dict をチェックします。それが存在しない場合は、データベースから値をロードします。このメソッドはDeferredAttribute
オブジェクト記述子をバイパスするため、値が存在しない場合は値が読み込まれません。
これはDjango 1.4と1.7で、おそらくその間のバージョンで機能します。Django 1.8はやがてget_deferred_fields()
を導入することに注意してください。クラスの内部とのこのすべての干渉に取って代わるメソッド。