Я работал над созданием подкласса db.Model, который автоматически кэшируется, т.е.
- instance.put сохранит сущность в memcache перед сохранением в хранилище данных
- class.get_by_key_name сначала проверит кеш, а в случае пропуска перейдет в хранилище данных, чтобы извлечь его и кэширует после извлечения
Я разработал подход ниже (который, кажется, работает для меня), но у меня есть несколько вопросов:
- Я прочитал статью Ника Джонсона о эффективной модели memcaching , в которой предлагается реализовать сериализацию для memcache через буферы протокола. Глядя на исходный код API memcache в SDK, похоже, что Google уже реализовал сериализацию protobuf по умолчанию. Правильна ли моя интерпретация?
- Я упускаю некоторые важные детали (которые могут получить меня в будущем) в том, как я подклассифицирую db.Model или переопределяю два метода?
- Есть ли более эффективный способ реализации того, что я сделал ниже?
- Существуют ли руководящие указания, контрольные показатели или рекомендации для случаев, когда такое кэширование сущностей имеет смысл с точки зрения производительности? Или будет всегда иметь смысл кэшировать сущности? В связи с этим, следует ли мне что-то читать о том, что Google не предоставил кэшированную модель в API моделирования? Не слишком ли много особых случаев, чтобы о них думать?
Ниже моя текущая реализация. Буду очень признателен за любые указания и предложения по кэшированию сущностей (даже если ваш ответ не является прямым ответом на один из 4 вопросов выше, но имеет отношение к теме в целом).
from google.appengine.ext import db
from google.appengine.api import memcache
import os
import logging
class CachedModel(db.Model):
'''Subclass of db.Model that automatically caches entities for put and
attempts to load from cache for get_by_key_name
'''
@classmethod
def get_by_key_name(cls, key_names, parent=None, **kwargs):
cache = memcache.Client()
# Ensure that every new deployment of the application results in a cache miss
# by including the application version ID in the namespace of the cache entry
namespace = os.environ['CURRENT_VERSION_ID'] + '_' + cls.__name__
if not isinstance(key_names, list):
key_names = [key_names]
entities = cache.get_multi(key_names, namespace=namespace)
if entities:
logging.info('%s (namespace=%s) retrieved from memcache' % (str(entities.keys()), namespace))
missing_key_names = list(set(key_names) - set(entities.keys()))
# For keys missed in memcahce, attempt to retrieve entities from datastore
if missing_key_names:
missing_entities = super(CachedModel, cls).get_by_key_name(missing_key_names, parent, **kwargs)
missing_mapping = zip(missing_key_names, missing_entities)
# Determine entities that exist in datastore and store them to memcache
entities_to_cache = dict()
for key_name, entity in missing_mapping:
if entity:
entities_to_cache[key_name] = entity
if entities_to_cache:
logging.info('%s (namespace=%s) cached by get_by_key_name' % (str(entities_to_cache.keys()), namespace))
cache.set_multi(entities_to_cache, namespace=namespace)
non_existent = set(missing_key_names) - set(entities_to_cache.keys())
if non_existent:
logging.info('%s (namespace=%s) missing from cache and datastore' % (str(non_existent), namespace))
# Combine entities retrieved from cache and entities retrieved from datastore
entities.update(missing_mapping)
if len(key_names) == 1:
return entities[key_names[0]]
else:
return [entities[key_name] for key_name in key_names]
def put(self, **kwargs):
cache = memcache.Client()
namespace = os.environ['CURRENT_VERSION_ID'] + '_' + self.__class__.__name__
cache.set(self.key().name(), self, namespace=namespace)
logging.info('%s (namespace=%s) cached by put' % (self.key().name(), namespace))
return super(CachedModel, self).put(**kwargs)