2009年9月1日星期二

App Engine的優化技巧(一)- 避免ReferenceProperty讀取資料庫

Google App Engine是一項以CPU時間、資料庫存取次數及其他限制等作收費依據的服務,曾經有過每天CPU時間上限達到46小時的日子,後來降至6.5小時,吸引力的確大不如前,眾用戶變得要過著勒緊褲頭的生活,這時候優化的技術就十分重要。

官方文件中提過不少的技巧,這裏就說一說官方文件中沒有記載的,第一個是關於ReferenceProperty的自動載入、轉換。

參考以下代碼:
class Author(db.Model):
name = db.StringProperty()

class Story(db.Model):
author = db.ReferenceProperty(Author)

story = db.get(story_key)
author_name = story.author.name
ReferenceProperty類別的模型為金鑰值(db.Key()),指向其他的記錄。以上叫做Story的模型有一個叫做author的ReferenceProperty,代表該故事的作者,而作者的資料則記錄在Author模型中,這種做法在SQL資料庫裏也很常見,沒什麼特別。
story = db.get(story_key)

這句在BigTable中取得一項Story記錄(又或者叫做實體模型、Model instance),要留意就是story.author_name所儲存的只是Key,並不包括作者的姓名等記錄在Author的資料。
author_name = story.author.name

當執行了以上的命令時,author_name儲存了什麼呢?答案是Author的實體的模型(model instance),並非以上所說的Key值。

這是ReferenceProperty的「功能」,一項自動載入、轉換的能力,跟據說明文件:
「ReferenceProperty 屬性值可以當做被參考實體的模型實例來使用。若參考的實體不在記憶體中,使用其屬性做為實例時,會自動從資料存放區擷取該實體。

A ReferenceProperty property value can be used as if it were the model instance of the referenced entity. If the referenced entity is not in memory, using the property as an instance automatically fetches the entity from the datastore.」
Source : http://code.google.com/intl/en/appengine/docs/python/datastore/entitiesandmodels.html#References

故此在story.author_name被第一次存取時,GAE就會自動在資料庫中取出記錄,很方便的功能吧?但別忘了,這會存取資料庫,代表會消耗有限的配額。

如果真的需要到這項資料,消耗也是沒有辦法的,但如果在其他的query中已取得這項Author的實體模型,也就冤枉了。

另外有種情況也會造成無辜的消耗,就是創建URL時,現在很喜歡這樣的URL風格:
xxxxxxx/$author_id/$story_id

當author_id=768,story_id = 10045時,就會變成是:
xxxxxxx/768/10045

假設在你的程式裏,已經擁有一個Story的實體,然後你想輸出以上的URL,基於需要用到Author的ID,你可能會這樣寫:
author_id = story.author_name.key().id_or_name()

而很不幸地,雖然在Story的實體中有記錄Author的Key,但這種寫法會導至GAE進行一次資料庫存取 - 浪費了配額下載了你不需要的資料。

這時候需要迂迴一點的寫法:
property = getattr(Story, "author_name")
author_key = property.get_value_for_datastore(story)
author_id = author_key.id_or_name()
這樣就不會造成資料庫的存取,為你省下不少的配額。

註: getattr(story,"author_name")的結果也是會導至資料庫的讀取

沒有留言:

Creative Commons License
本網誌Ben Lau製作,以共享創意署名-非商業性-相同方式共享 3.0 香港 授權條款釋出。