AppEngine Model Memcaching的最佳方法是什么?

时间:2010-02-19 21:21:51

标签: google-app-engine memcached google-cloud-datastore

目前我的应用程序在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()

3 个答案:

答案 0 :(得分:4)

Memcache调用仍然使用或不使用protobuf来pickle对象。使用protobuf对象时,Pickle更快,因为它有一个非常简单的模型

普通的pickle对象比protobuf + pickle对象大,因此它们在Memcache上节省了时间,但是在进行protobuf转换时有更多的处理器时间

因此,一般来说,任何一种方法都可以解决相同问题......但

你应该使用protobuf的原因是它可以处理模型版本之间的变化,而Pickle会出错。这个问题有一天会让你感到害怕,所以最好早点处理它

答案 1 :(得分:1)

App Engine中的pickle和protobufs都很慢,因为它们是用纯Python实现的。我发现使用像str.join这样的方法编写我自己的简单序列化代码往往会更快,因为大部分工作是在C中完成的。但这只适用于简单的数据类型。

答案 2 :(得分:1)

更快速地做到这一点的一种方法是将你的模型变成一个字典并使用原生的eval / repr函数作为你的(de)序列化器 - 当然要谨慎,与邪恶的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

我欢迎对此进行速度测试,我认为这比其他方法快得多。此外,如果您的模型在此期间以某种方式发生了变化,您也没有任何风险。

下面是序列化假实体的示例。特别关注datetime(已创建)以及引用属性(子域):

### FAKE MODEL ENTITY ###
{'status':u'admin','session_expiry':无,'first_name':u'Louis','last_name':u'Le Sieur','modified_by':无,'password_hash':u'a9993e364706816aba3e25717000000000000000', 'language':u'fr','created':datetime.datetime(2010,7,18,21,50,11,750000),'modified':无,'created_by':无,'email':你' chou@glou.bou','key':'agdqZXJlZ2xlcgwLEgVMb2dpbhjmAQw','session_ref':无,'_ cls':'models.Login','groups':[],'email___password_hash':u'chou@glou.bou+ a9993e364706816aba3e25717000000000000000','subdomain':datastore_types.Key.from_path(u'Subdomain',229L,_app = u'jeregle'),'允许':[],'权限':[]}


我个人也使用静态变量(比memcache更快)在短期内缓存我的实体,并在服务器发生变化或由于某种原因刷新其内存时获取数据存储(事实上经常发生这种情况)。