【问题标题】:How to reduce number of requests to the Datastore如何减少对数据存储的请求数量
【发布时间】:2012-04-28 23:02:38
【问题描述】:

根据 AppStats,当使用 200 个文档和 1 个 DocUser 运行以下脚本时,脚本大约需要 5000 毫秒。罪魁祸首是 lastEditedBy (datastore_v3.Get) 的每次锁定都会向数据存储发出请求,每次锁定需要 6-51 毫秒。

我正在尝试做的事情是使显示具有多个属性的许多实体成为可能,其中一些属性是从其他实体派生的。永远不会有大量实体(

我尝试通过缓存 DocUser 实体来进行优化,但如果不对数据存储区发出新请求,我无法从上面的查询中获取 DocUser 键。

1) 这有意义吗 - 我遇到的延迟正常吗?

2) 有没有办法在不向数据存储区发出额外请求的情况下完成这项工作?

models.py

class Document(db.Expando):
    title = db.StringProperty()
    lastEditedBy = db.ReferenceProperty(DocUser, collection_name = 'documentLastEditedBy')  
...

class DocUser(db.Model):
    user = db.UserProperty()
    name = db.StringProperty()  
    hasWriteAccess= db.BooleanProperty(default = False)
    isAdmin = db.BooleanProperty(default = False)
    accessGroups = db.ListProperty(db.Key)
...

main.py

$out = '<table>'   
documents = Document.all()
for i,d in enumerate(documents):        
    out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
$out = '</table>'

【问题讨论】:

  • 你为什么要循环访问文档,然后每次都在循环中获取所有文档?您的 main.py 有问题。
  • 对不起,我的示例代码中有错误。现已修复。

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


【解决方案1】:

这是一个典型的反模式。您可以通过以下方式解决此问题:

【讨论】:

  • 感谢您的反馈。我现在明白 GAE 数据存储并不像我最初想象的那么先进和容易上手。我可能应该改用 NBC,但这将需要重写整个应用程序。我无法让尼克的代码工作,我看到他的博客上有一些 cmets 某些东西不再工作了。你最近测试过这个吗?
【解决方案2】:

一种方法是预取所有文档生成一个查找字典,键是 docuser.key(),值是 docuser.name。

    docusers = Docuser.all().fetch(1000)
    docuser_dict = dict( [(i.key(), i.name) for i in docusers] )

然后在您的代码中,您可以通过使用 get_value_for_datastore 获取 docuser.key() 从 docuser_dict 中获取名称,而无需从数据存储区中拉取对象。

    documents = Document.all().fetch(1000)
    for i,d in enumerate(documents):
        docuser_key = Document.lastEditedBy.get_value_for_datastore(d)
        last_editedby_name = docuser_dict.get(docuser_key)
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, last_editedby_name)

【讨论】:

    【解决方案3】:

    如果您想缩短实例时间,可以将单个同步查询分解为多个异步查询,这样可以在您执行其他工作时预取结果。不要使用 Document.all().fetch(),而是使用 Document.all().run()。您可能必须阻止您迭代的第一个查询,但是当它完成时,所有其他查询都将完成加载结果。如果您想获取 200 个实体,请尝试一次使用 5 个查询。

    q1 = Document.all().run(prefetch_size=20, batch_size=20, limit=20, offset=0)
    q2 = Document.all().run(prefetch_size=45, batch_size=45, limit=45, offset=20)
    q3 = Document.all().run(prefetch_size=45, batch_size=45, limit=45, offset=65)
    q4 = Document.all().run(prefetch_size=45, batch_size=45, limit=45, offset=110)
    q5 = Document.all().run(prefetch_size=45, batch_size=45, limit=45, offset=155)
    for i,d in enumerate(q1):        
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
    for i,d in enumerate(q2):        
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
    for i,d in enumerate(q3):        
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
    for i,d in enumerate(q4):        
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
    for i,d in enumerate(q5):        
        out += '<tr><td>%s</td><td>%s</td></tr>' % (d.title, d.lastEditedBy.name)
    

    我为我的蹩脚蟒道歉;但这个想法很简单。设置您的 prefetch_size = batch_size = limit,并立即启动所有查询。 q1 的尺寸较小,因为我们会先对其进行阻塞,而阻塞是浪费时间。到 q1 完成时,q2 将完成或几乎完成,而 q3-5 您将支付零延迟。

    详情请见https://developers.google.com/appengine/docs/python/datastore/async#Async_Queries

    【讨论】:

    • 谢谢。这很有趣,但我希望我不需要为了让我的应用程序合理执行而进行这些黑客攻击
    • 这并不是真正的 hack。这就是您释放 appengine 力量的方式。它可以轻松地并行执行 5-10 个查询,您可以将其包装在一个方法 def multiquery(...) 中。我一直在 appengine java 中这样做,并将我们的成本从 70 美元/天降低到 25 美元/天。使用异步一切。所有 appengine 服务都可以异步使用,这就是您将实例小时数降至最低的方法。
    • 另外,如果您的 DocUser 引用属性在同一实体上导致昂贵的重复gets(),您应该考虑在迭代时缓存每个查找属性的键,然后仅解析唯一键,所以你一次只能得到一个。我也建议异步执行此操作;如果可能的话。
    • 再次感谢您。我一直在思考这个问题,我不明白的一件事是,如果人们对将返回的实体数量一无所知,如何应用这种技术。你是如何在你的 multiquery(...) 方法中实现它的?
    • 或者,可以首先异步运行计数查询和 X 第一个结果的查询。如果计数大于 X,则可以并行运行额外的切片查询,并将结果与​​第一个查询相结合。如果 X 较小,则将简单地返回第一个查询的结果。进一步减少延迟可以通过最初启动 Y 异步切片查询来实现,这些查询总和为 X,并在必要时根据计数查询的结果将这些结果与其他查询相结合。这都可以由函数根据 X 的大小来处理。
    猜你喜欢
    • 2017-03-25
    • 2021-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 2016-02-25
    • 1970-01-01
    相关资源
    最近更新 更多