В настоящее время мои модели кэшей приложения в кэш-памяти как это:
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()
Вызов Memcache по-прежнему пикает объект с использованием или без использования protobuf. Pickle работает быстрее с объектом protobuf, поскольку у него очень простая модель
Обычные объекты pickle больше, чем объекты protobuf+pickle, поэтому они экономят время на Memcache, но процессорное время на преобразование protobuf больше
Поэтому в целом любой метод работает примерно одинаково... но
Причина, по которой вы должны использовать protobuf, в том, что он может обрабатывать изменения между версиями моделей, тогда как Pickle будет ошибаться. Эта проблема однажды укусит вас, так что лучше решить ее раньше
.Как pickle, так и protobufs работают медленно в App Engine, поскольку они реализованы на чистом Python. Я обнаружил, что написание собственного простого кода сериализации с использованием таких методов, как str.join, как правило, выполняется быстрее, поскольку большая часть работы выполняется на C. Но это работает только для простых типов данных.
Один из способов сделать это быстрее - превратить вашу модель в словарь и использовать встроенную функцию eval / repr в качестве (де) сериализатора - с осторожностью, конечно, как всегда с злой eval, но здесь должно быть безопасно, учитывая отсутствие внешнего шага.
Ниже приведен пример класса Fake_entity, реализующего именно это.
Сначала вы создаете свой словарь с помощью fake = Fake_entity (entity)
, затем вы можете просто сохранить свои данные через memcache.set (key, fake.serialize ())
. Serialize () - это простой вызов собственного словарного метода repr с некоторыми дополнениями, если вам нужно (например,добавить идентификатор в начало строки).
Чтобы получить его обратно, просто используйте 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
Я приветствовал бы тесты скорости на этом, я предполагаю, что это намного быстрее, чем другие подходы. Кроме того, у вас нет никаких рисков, если ваши модели тем временем как-то изменились.
Ниже показан пример того, как выглядит сериализованная поддельная сущность. Обратите особое внимание на дату и время (создано), а также на ссылочные свойства (субдомен):
### ПОДДЕЛКА МОДЕЛИ СУБЪЕКТА ###
Лично я также использую статические переменные (быстрее, чем memcache) для краткосрочного кэширования моих объектов и получения хранилища данных, когда сервер изменился или его память была очищена по какой-то причине (что на самом деле случается довольно часто).