2009年6月18日 星期四

資料計數器

Datastore API 的限制


在使用 Datastore API 時有個限制,就是每作一次 query ,最多能存取到的資料數量為 1000 筆(註1),也因為這個限制,所以也不能直接使用 Query 物件下的 count() 方法來計算資料數量。

舉例來說,如果要設計一個網路商店,每一件商品是一個 Product entity,假如網站中的商品數量超過 1000 筆,那這樣的程式碼:
from google.appengine.ext import db

class Product(db.Model):
...
# product 各欄位

...
query = Product.all()
number_of_products = query.count()

並不會幫你統計出究竟有多少 Product entity,因為 query 的結果最多就是 1000 筆資料。

手動記錄


為了解決這樣的問題,其實可以自己定義一個「計數器」的資料,用來統計究竟有多少資料,於是立刻可以寫出這樣的 data model:
from google.appengine.ext import db

class Counter(db.Model):
count = db.IntegerProperty(required=True, default=0)

有了這樣的資料模型,便能夠用來統計網站中任何資料的數量。我們可以經由不同的 key_name 來區分出不同的計數器,根據上述的例子,便可以在新增 Product 時,在計數器上加1以便統計。
# 新增 product 的程式片段...
...
p = Product(........) # 新增 product entity
p.put()

# 根據 key_name 取得 counter
counter = Counter.get_by_key_name('product_counter')
if counter is None:
# 如果 counter 不存在,則建立一個新的,別忘了指定 key_name
counter = Counter(key_name='product_counter')

counter.count += 1
counter.put()

以此類推,當要刪除一個或多個 Product entities 時,也要同步更新對應的計數器資料,以保持資料數量的一致性(consistency)。



  1. 之後會介紹如何讀出超過1000筆的資料

屬性中的 indexed=False

如果你的程式中,有個 data model 的 property 很多,那資料在存取時會隨著 property 數量的成長而降低效能(尤其是發生在呼叫 put() 來儲存資料時),這是因為在儲存或更新資料時,datastore 也會針對這些 property 作一些 index 的動作,使得整個操作的時間變長。

如果確定不會用某個 property 來作資料查詢 filter 的條件,那麼可以在定義 property 時加上 indexed=False 參數,提示 datastore 這個 property 不要做 index,範例程式如下:
from google.appengine.ext import db

class Foo(db.Model):
....
bar = db.StringProperty(indexed=False)

2009年6月15日 星期一

為儲存的內容加上標籤(簡單版)

現在很多網站都會為儲存的內容加上標籤,像 Flickr 的每一張相片都可以設定標籤:



或是像 delicious 的書籤也可以設定標籤:



在 App Engine 上開發應用程式,如果要為儲存的內容加上標籤的支援,那麼在資料模型(Data Model)的定義時,可以加上一個 property:
from google.appengine.ext import db
class MyContent(db.Model):
....
tags = db.StringListProperty()

如此一來,tags就可以儲存由字串所組成的 list資料,也就是可以儲存 MyContent 物件的標籤。

假設使用者透過表單送出標籤的資料,而標籤的資料是一個以逗號(,)隔開的字串,那麼要儲存標籤的作法就是:
....
tags_string = self.request.get('tags')
content = MyContent()
....
content.tags = map(lambda x: x.strip(), tags_string.split(','))
content.put()
....

雖然 tags_string.split(',') 已經產生一個由字串所組成的 list 了,但是使用者輸入的字串,可能會在逗號的前後留下空白,所以用了 map 函數將 list 中的每個元素空白消掉(strip 函數)

如果要取出含有某個標籤值(如:food)的內容,則可以直接使用 GQL 中特殊的語法來取出:
....
query = MyContent.gql('WHERE tags = :1', 'food')
for content in query:
....

因為 GQL 的設計,雖然 tags 欄位是一個 list,但是只要比對的元素有出現在 list 中,則 = 運算的結果就會是 True,所以就能夠輕易地根據 list 中的元素來查詢資料。

本部落格的宗旨

筆者因為深感 Google App Engine 的中文討論資源不足,所以打算把自己的研究整理成專門的 blog 來發表,順便與同好們互相討論學習。

未來這個部落格將會不定期更新一些在 Google App Engine 上撰寫程式的技巧及範例,主要會以 Python 版本為主,若有筆誤或是觀念錯誤的部份,還請各位讀者不吝賜教。