Автоматически кэшированные модели в App Engine - PullRequest
1 голос
/ 09 ноября 2011

Я работал над созданием подкласса db.Model, который автоматически кэшируется, т.е.

  • instance.put сохранит сущность в memcache перед сохранением в хранилище данных
  • class.get_by_key_name сначала проверит кеш, а в случае пропуска перейдет в хранилище данных, чтобы извлечь его и кэширует после извлечения

Я разработал подход ниже (который, кажется, работает для меня), но у меня есть несколько вопросов:

  1. Я прочитал статью Ника Джонсона о эффективной модели memcaching , в которой предлагается реализовать сериализацию для memcache через буферы протокола. Глядя на исходный код API memcache в SDK, похоже, что Google уже реализовал сериализацию protobuf по умолчанию. Правильна ли моя интерпретация?
  2. Я упускаю некоторые важные детали (которые могут получить меня в будущем) в том, как я подклассифицирую db.Model или переопределяю два метода?
  3. Есть ли более эффективный способ реализации того, что я сделал ниже?
  4. Существуют ли руководящие указания, контрольные показатели или рекомендации для случаев, когда такое кэширование сущностей имеет смысл с точки зрения производительности? Или будет всегда иметь смысл кэшировать сущности? В связи с этим, следует ли мне что-то читать о том, что 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)

Ответы [ 3 ]

2 голосов
/ 10 ноября 2011

Вместо того, чтобы заново изобретать колесо, почему бы не переключиться на NDB , который уже реализует мемкаширование экземпляров модели?

1 голос
/ 09 ноября 2011

Вы можете проверить статью Ника Джонсона о добавлении пре и пост-хуков для классов модели данных в качестве альтернативы переопределению get_by_key_name. Таким образом, ваш хук может работать даже при использовании db.get и db.put.

Тем не менее, я обнаружил в своем приложении, что у меня были более существенные улучшения производительности, кеширующие вещи на более высоком уровне - например, весь контент, который мне нужен для отображения всей страницы, или сам HTML страницы, если это возможно.

Вы также можете проверить библиотеку asynctools , которая поможет вам параллельно выполнять запросы к хранилищам данных и кэшировать результаты.

0 голосов
/ 09 ноября 2011

Многие полезные советы от Ника Джонсона, которые вы хотите реализовать, уже реализованы в модуле appengine-mp .например, сериализация через protocolbuf или объекты предварительной выборки.

О вашем методе get_by_key_names вы можете проверить код .Если вы хотите создать свой собственный слой db.Model, возможно, это поможет вам, но вы также можете внести свой вклад в улучшение существующей модели.;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...