2009年7月6日 星期一

支援交易運算的計數器

支援交易運算


在許多資料庫運算中,為了保持相關的資料庫運算結果能夠不被分割地將同時送進資料庫(也就是一旦有一個操作發生失敗,則整串操作都會同時取消),所以提供了「交易」(transaction)的運算。

Google App Engine 上的 datastore 當然也支援「交易」運算,在上述的例子中,共有兩個資料庫運算--Product entity 的寫入以及 Counter entity 的更新,試著想像這兩個操作若各自(或同時)發生錯誤時會有什麼問題,若是 Product entity 在寫入時發生錯誤,程式就中斷了,那就沒什麼太大的問題,若是 Product entity 成功寫入 datastore,但 Counter entity 在更新時發生錯誤,這時就會造成計數器的統計數量不一致,這時候就適合使用交易運算了。

交易運算的限制


在 Google App Engine 中,交易運算只能操作相同 entity group 的資料,如果我們要將 ProductCounter 的新增動作在同一個交易運算中完成的話,就必須讓它們成為同一個 entity group。

要成為同一個 entity group,不管是 Product 還是 Counter 都需要一個共同的 parent,因此需要一個額外的 data model 來作為這個 entity group 的 root,這裡我提供一個 Index 的 model,順便用來作為每一筆 Product 資料的「序號產生器」 :p
class Index(db.Model):
max_index = db.IntegerProperty(required=True, default=0)

有了這個 model 後,新增 Product 及更新 Counter 的動作就可以改寫為:
# 新增 product 的程式片段...
...
# 從 Index 中取出 Product 的 index
ind = Index.get_by_key_name('product')
if ind is None:
ind = Index()
ind.max_index += 1
ind.put()

# 新增 product entity 並設定 parent 為 ind
p = Product(parent=ind, key_name="product_%d" % ind.max_index,........)
p.put()

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

counter.count += 1
counter.put()

在新增 ProductCounter 時,都加上指定 parent 的參數,以此將這些資料建構成一個 entity group,然而,一旦資料在 entity group 中,在取出時也要加上 parent 參數。

作成交易運算的函式


因此,我們可以把上面的程式碼包成一個函式,比方說是 create_product,這樣就可以利用 Datastore API 中的 run_in_transaction 函式來作成交易運算了。

....
def create_product():
# 放入上述的程式碼

....
# 新增資料時...
from google.appengine.ext import db
db.run_in_transaction(create_product)


如此一來,只有當 Index, ProductCounter 的資料操作都成功時,更新的資料才會進入 datastore 中。

沒有留言:

張貼留言