如何通过预先获取ReferenceProperty属性来防止过多的RPC调用?

时间:2012-07-14 23:17:48

标签: python google-app-engine memcached jinja2

我正在尝试显示一个包含约800个实体的表格,并且在保持它真正慢的问题上存在问题。 (比如15-20秒慢。)我成功实现了memcache,但因为我引用了每个子实体的父模型,所以它仍然会导致每个800的datastore_v3.Get并且速度非常慢。

然后我实施了Nick Johnson的ReferenceProperty prefetching并且无法解决以下错误:

[... snipped ...]
File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2/webapp2.py", line 570, in dispatch
  return method(*args, **kwargs)
File "/myurl/mypythoncode.py", line 67, in get
  prefetch_refprops(entitylist, ChildModel.parent_program.name)
File "/myurl/mypythoncode.py", line 36, in prefetch_refprops
  fields = [(entity, prop) for entity in entities for prop in props]
TypeError: 'NoneType' object is not iterable

型号:

这是两个相关的模型:

class ParentModel(db.Model):
  name = db.StringProperty()
  # currently 109 of these

class ChildModel(db.Model):
  name = db.StringProperty()
  parent_program = db.ReferenceProperty(ParentModel)
  website = db.StringProperty()
  # currently 758 of these

Python代码:

在我的Python代码中,我使用的是Nick Johnson的efficient model memcachingReferenceProperty prefetching技巧。 (我在下面列出了ReferenceProperty预取,但不包括memcaching代码。)

class AllEntities(webapp2.RequestHandler):
  def get(self):
    entitylist = deserialize_entities(memcache.get("entitylist"))
    entityref = prefetch_refprops(entitylist, ChildModel.parent_program.name)
    if not entitylist:
      entitylist = ChildModel.all().fetch(None)
      entityref = prefetch_refprops(entitylist, ChildModel.parent_program.name)
      memcache.set("entitylist", serialize_entities(entitylist))
    context = {
      'entitylist': entitylist,
    }
    self.response.out.write(template.render(context))

def prefetch_refprops(entities, *props):
    fields = [(entity, prop) for entity in entities for prop in props]
    ref_keys = [prop.get_value_for_datastore(x) for x, prop in fields]
    ref_entities = dict((x.key(), x) for x in db.get(set(ref_keys)))
    for (entity, prop), ref_key in zip(fields, ref_keys):
        prop.__set__(entity, ref_entities[ref_key])
    return entities

Jinja2模板:

我的Jinja2模板引用了“entitylist”中的可迭代“条目”,还引用了parent_program.name和parent_program.key()。id()

{% for entry in entitylist %}
  <tr>
    <td><a href="{{ entry.website}}">{{ entry.website }}</a></td>
    <td><a href="/urlcategory/view?entityid={{ entry.parent_program.key().id() }}">{{ entry.parent_program.name }}</td>
  </tr>
{% endfor %}

我已经更换了这一行:

entityref = prefetch_refprops(entitylist, ChildModel.parent_program.name)

entityref = prefetch_refprops(entitylist, ChildModel.parent_program)

以及其他包含“.name”和“.key()。id()”的变体。当我使用“.key()。id()”时,我收到错误:

AttributeError: 'ReferenceProperty' object has no attribute 'key'

我错过了什么或搞砸了什么?我真的很感激任何帮助!

1 个答案:

答案 0 :(得分:1)

杰德,你做对了:)

两项改进:

  1. 您不需要指定预取的返回值,因为它未被使用,公司列表将被修改。
  2. 我使用prefetch_refprops的略微修改版本来处理未填充引用属性的情况。

    def prefetch_refprops(entities, *props):
        fields = [(entity, prop) for entity in entities for prop in props]
        ref_keys_all = [prop.get_value_for_datastore(x) for x, prop in fields]
        ref_keys = [ref_key for ref_key in ref_keys_all if ref_key is not None]
        ref_entities = dict((x.key(), x) for x in db.get(set(ref_keys)))
        for (entity, prop), ref_key in zip(fields, ref_keys_all):
            if ref_key and ref_entities[ref_key]:
                prop.__set__(entity, ref_entities[ref_key])
            else:
                prop.__set__(entity, None)
        return entities
    
  3. 我们在生产代码中使用它,它会产生真正的不同。下面是在构建模板值的一些代码上打开/关闭预取的示例。

    (run:   real_time, Get #rpcs, RunQuery #rpcs)
    Before:   5044 ms,       132,    101
    After:    2214 ms,        53,     11
    

    我们的代码正在进行的另一个重链梯操作是每个对象的ref_set上的count(),我们将在不久的将来替换对象上的值。