2012-04-28 74 views
0

根据AppStats,当运行200个文档和1个DocUser时,脚本大约需要5000ms。总结在于,对于每个需要6-51ms的lastEditedBy(datastore_v3.Get)的每个锁定,都存在对数据存储的请求。如何减少对数据存储的请求数

我想要做的是使某些东西,使显示许多实体与几个属性,其中一些是从其他实体派生。永远不会有大量的实体(< 5000),并且由于这更像是一个管理界面,所以永远不会有很多同时用户。

我试图通过缓存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>' 
+0

为什么你在文档上循环,然后每次在循环中读取所有文档?你的main.py有问题。 – mjibson 2012-04-29 00:40:55

+0

对不起,我的示例代码中有错误。现在修复。 – 2012-04-29 08:27:54

回答

1

其中一种方法是预取所有文件以创建查找字典,其中的键为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) 
+0

谢谢你,这个作品很棒。 – 2012-04-29 22:20:31

4

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

+0

感谢您的反馈。我现在明白,GAE数据库不像我以前想的那样先进和容易。我应该使用NBC来代替,但这会使得有必要重写整个应用程序。 我无法让Nick的代码正常工作,我发现他的博客上有一些评论,说明某些内容不再适用。你最近测试过了吗? – 2012-04-29 22:13:57

1

如果要剪切的实例时,你可以打破单一的同步查询到多个异步查询,而你做其他工作,可以预取结果。而不是使用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) 

我为我的cr py蟒蛇道歉;但这个想法很简单。设置您的prefetch_size = batch_size =限制,并立即启动所有查询。 q1的尺寸较小,因为我们会先阻挡它,而阻塞是浪费时间。当q1完成时,q2将​​完成或几乎完成,并且q3-5将支付零延迟。

有关详细信息,请参阅https://developers.google.com/appengine/docs/python/datastore/async#Async_Queries

+0

谢谢。这很有趣,但我希望我不需要做这些黑客来让我的应用程序合理地运行。 – 2012-04-30 10:40:41

+0

这不是真正的黑客。这就是你如何解开appengine的力量。它可以很容易地并行执行5-10个查询,并且可以用单个方法def multiquery(...)将其包装。我在appengine java中一直这样做,并将我们的成本从每天70美元降至每天25美元。 使用异步的一切。所有的appengine服务都可以异步使用,这就是您将实例小时数降至最低的方法。 – Ajax 2012-04-30 14:28:14

+0

另外,如果您的DocUser引用属性在同一个实体上导致了昂贵的重复获取(),则应该考虑在迭代时缓存每个查找属性的键,然后只解析唯一键,以便仅获取一个一旦。我会建议这样做异步以及;如果可能的话。 – Ajax 2012-04-30 14:47:55