Каков наилучший способ сделать AppEngine Model Memcaching?
В настоящее время мое приложение кэширует модели в memcache следующим образом:
memcache.set("somekey", aModel)
Но сообщение Никса в http://blog.notdot.net/2009/9/Efficient-model-memcaching предполагает, что первое преобразование его в protobuffers намного более эффективно. Но после запуска некоторых тестов я обнаружил, что он действительно меньше по размеру, но на самом деле медленнее (~ 10%).
У других есть такой же опыт, или я делаю что-то неправильно?
Результаты тестов: http://1.latest.sofatest.appspot.com/?times=1000
import pickle
import time
import uuid
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp import util
from google.appengine.datastore import entity_pb
from google.appengine.api import memcache
class Person(db.Model):
name = db.StringProperty()
times = 10000
class MainHandler(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
m = Person(name='Koen Bok')
t1 = time.time()
for i in xrange(int(self.request.get('times', 1))):
key = uuid.uuid4().hex
memcache.set(key, m)
r = memcache.get(key)
self.response.out.write('Pickle took: %.2f' % (time.time() - t1))
t1 = time.time()
for i in xrange(int(self.request.get('times', 1))):
key = uuid.uuid4().hex
memcache.set(key, db.model_to_protobuf(m).Encode())
r = db.model_from_protobuf(entity_pb.EntityProto(memcache.get(key)))
self.response.out.write('Proto took: %.2f' % (time.time() - t1))
def main():
application = webapp.WSGIApplication([('/', MainHandler)], debug=True)
util.run_wsgi_app(application)
if __name__ == '__main__':
main()
Ответы
Ответ 1
Вызов Memcache все еще разжигает объект с использованием или без использования protobuf. Pickle быстрее с объектом protobuf, так как он имеет очень простую модель
Обычные объекты рассола больше, чем объекты protobuf + pickle, поэтому они экономят время на Memcache, но время выполнения протобуфа больше времени процессора
Поэтому, как правило, любой из методов работает примерно одинаково... но
Причина, по которой вы должны использовать protobuf, может обрабатывать изменения между версиями моделей, тогда как Pickle будет ошибочной. Эта проблема укусит вас однажды, поэтому лучше всего справиться с ней раньше
Ответ 2
Оба pickle и protobufs медленны в App Engine, поскольку они реализованы в чистом Python. Я обнаружил, что создание собственного простого кода сериализации с использованием таких методов, как str.join, имеет тенденцию быть быстрее, поскольку большая часть работы выполняется на C. Но это работает только для простых типов данных.
Ответ 3
Один из способов сделать это быстрее - превратить вашу модель в словарь и использовать встроенную функцию eval/repr в качестве сериализаторов (de) с осторожностью, как всегда со злым eval, но это должно быть что здесь нет внешнего шага.
Ниже пример класса Fake_entity, реализующего именно это.
Сначала вы создаете словарь через fake = Fake_entity(entity)
, тогда вы можете просто сохранить свои данные через memcache.set(key, fake.serialize())
. Сериализация() является простым вызовом метода родного словаря, с некоторыми дополнениями, если вам нужно (например, добавить идентификатор в начале строки).
Чтобы вернуть его, просто используйте fake = Fake_entity(memcache.get(key))
. Объект Fake_entity - это простой словарь, ключи которого также доступны как атрибуты. Обычно вы можете получить доступ к своим объектам, за исключением того, что referenceProperties дает ключи вместо того, чтобы извлекать объект (что на самом деле очень полезно). Вы также можете получить() фактический объект с помощью fake.get() или более интересно, изменить его, а затем сохранить с помощью fake.put().
Он не работает со списками (если вы извлекаете несколько объектов из запроса), но их можно легко отрегулировать с помощью функций соединения/разделения с использованием идентификатора типа "### FAKE MODEL ENTITY ###" в качестве разделителя. Используйте только с db.Model, вам потребуется небольшая настройка для Expando.
class Fake_entity(dict):
def __init__(self, record):
# simple case: a string, we eval it to rebuild our fake entity
if isinstance(record, basestring):
import datetime # <----- put all relevant eval imports here
from google.appengine.api import datastore_types
self.update( eval(record) ) # careful with external sources, eval is evil
return None
# serious case: we build the instance from the actual entity
for prop_name, prop_ref in record.__class__.properties().items():
self[prop_name] = prop_ref.get_value_for_datastore(record) # to avoid fetching entities
self['_cls'] = record.__class__.__module__ + '.' + record.__class__.__name__
try:
self['key'] = str(record.key())
except Exception: # the key may not exist if the entity has not been stored
pass
def __getattr__(self, k):
return self[k]
def __setattr__(self, k, v):
self[k] = v
def key(self):
from google.appengine.ext import db
return db.Key(self['key'])
def get(self):
from google.appengine.ext import db
return db.get(self['key'])
def put(self):
_cls = self.pop('_cls') # gets and removes the class name form the passed arguments
# import xxxxxxx ---> put your model imports here if necessary
Cls = eval(_cls) # make sure that your models declarations are in the scope here
real_entity = Cls(**self) # creates the entity
real_entity.put() # self explanatory
self['_cls'] = _cls # puts back the class name afterwards
return real_entity
def serialize(self):
return '### FAKE MODEL ENTITY ###\n' + repr(self)
# or simply repr, but I use the initial identifier to test and eval directly when getting from memcache
Я бы приветствовал тесты скорости на этом, я бы предположил, что это намного быстрее, чем другие подходы. Кроме того, у вас нет никаких рисков, если ваши модели каким-то образом изменились.
Ниже пример того, как выглядит сериализованный поддельный объект. Обратите особое внимание на datetime (created), а также на эталонные свойства (субдомен):
### FAKE MODEL ENTITY ###
{'status': u'admin ',' session_expiry ': None,' first_name ': u'Louis', 'last_name': u'Le Sieur ',' modified_by ': None,' password_hash ': u'a9993e364706816aba3e25717000000000000000', 'language': u'fr ',' created ': datetime.datetime(2010, 7, 18, 21, 50, 11, 750000),' modified ': None,' created_by ': None,' email ': u' [email protected] ',' key ':' agdqZXJlZ2xlcgwLEgVMb2dpbhjmAQw ',' session_ref ': None,' _cls ':' models.Login ',' groups ': [],' email___password_hash ': u'[email protected]+ a9993e364706816aba3e25717000000000000000 ',' subdomain ': datastore_types.Key.from_path (u'Subdomain', 229L, _app = u'jeregle '),' allowed ': [],' permissions ': []}
Лично я также использую статические переменные (быстрее, чем memcache), чтобы кэшировать мои объекты в краткосрочной перспективе, и извлекать хранилище данных, когда сервер изменился или по какой-то причине покраснели память (что часто случается довольно часто).