實戰 Datastore (2)

在 Datastore 中,最重要的就是擴展性(scalability)及平行運算的考量,Datastore 之所以在目前的 API 或是 GQL 上限制重重,就是希望寫程式的人能在資料的擺放上用點心思來考慮怎麼擺資料才對擴展性及平行運算有幫助。

資料的寫入衝突 (Write Contention)

資料庫中最常發生效能瓶頸的地方就在於資料寫入的部份,如果對於同步化的議題有一些了解的人都知道,如果今天資料只是單純被讀取,那不會有什麼不一致的問 題,甚至還可以把資料用快取(cache)儲存起來或是增加讀取的管線(pipeline)(大家一起讀取都沒關係)以增進效能。但是資料會被寫入(或更 新)時就麻煩很多了,雖然在 cache (buffer) 上可以利用常見的技巧(如:write through 或 write back)來舒緩一些寫入的壓力,但為了保持資料一致性,寫入及作交易(transaction)時,常常就必須要先把資料鎖住,等到寫完了才能繼續其它 的運算,如此一來,不同來源的寫入需求就很難同時(平行地)處理,於是產生了寫入衝突(Write Contention)。

在 Datastore 上,常會被寫入的資料會建議多放幾份,以方便讓寫入的需求可以同時進行,增加寫入的效能。這種概念跟 RAID 5 的方式很類似:你把資料分成幾個碎片(shard)然後分散在不同的地方,這樣就可以增加同時寫入的機會。在 Datastore 中,你可以利用 entity 的 key_name 來把資料分散成碎片,達到分散資料的目的。

這裡先離題解釋一下 Datastore 上 entity 的幾個名詞:key, ID, key_name。這三個欄位會自動出現在每個 entity 上,所以不必在 Model 中宣告它們。每一個被放上 Datastore 的 entity 都會有一個唯一的 key,這個 key 是根據一些屬性然後 hash 出來的值,雖然 entity 間的 key 值是唯一,但對於寫程式的人來說,任兩個 entity 的 key 就沒有「看得出來」的規則可循。於是 key_name 這個欄位就誕生了,你可以在建立資料時指定一個「唯一」的 key_name,其實就可以把 key_name 想成是 key 的一個別名(alias),所以 key_name 也必須是唯一的,只是 key_name 你可以自己設定,就讓你有機會在 entity 間建立一個虛擬的規則(等下會用例子說明)。而 ID 則是一個數字,也可以自己設定,當然也必須是唯一的。

以下直接以一個範例說明,假如你有一個 counter 用來記錄某個 Foo entity 在資料庫中放了幾份,那麼你可以把記錄 Foo entity 的 counter 分成3份(打個比方而已,要放幾份隨便你),然後分別給他們 foo0, foo1, foo2 的 key_name,所以你在寫入 counter 的 code 就可以這樣寫:


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

def add_count(counter_name):
def tx():
import random
index = random.randint(0, 2)
shard_name = counter_name + str(index)
counter = Counter.get_by_key_name(shard_name)
if counter is None:
counter = Counter(key_name=shard_name,
name=counter_name)
counter.count += 1
counter.put()
db.run_in_transaction(tx)

這段 code 會隨機產生 foo0, foo1, foo2 這些 key_name,然後再透過 Datastore API 中的 get_by_key_name 來取得資料,如果還沒有這份 entity 就產生一個,當然除了 key_name 之外,也用一個 name 欄位來表示該 counter 是在 count 誰。所以取得 counter 正確值的時候,就得要把所有碎片的值加總起來:


def get_counter(counter_name):
total = 0
counters = Counter.gql('WHERE name = :1', counter_name)
for counter in counters:
total += counter.count
return total

這樣就相當於同時擺了很多份同一個 name 的 counter,在寫入時就可以同時寫入而增加效能了。

參考文章:

  • 安打王

    Excellent!寫的真詳細阿!

    比較好奇的是當出現 Write Contention 時,cpu loading 通常會飆很高(以mysql來說)。不知會不會吃掉 GAE 的 cpu quota?如果會的話,那 Datastore 的資料儲存方式就是使用 GAE 不可不學的一門課題了。

  • Pingback: (依主題重組):電腦技術領域 « eweibookmark()

  • James

    ericsk大哥您好,我對於google app engine有很高度的興趣,但礙於
    我對於java並不熟,所以有進入障礙,是否能跟你索取關於Datastore的相關
    範例檔,謝謝。
    e-mail:a70103@gmail.com