2012-04-28 76 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