Python 2 App Engine NDB 用戶端程式庫總覽

Google Datastore NDB 用戶端程式庫可讓 App Engine Python 應用程式連線至 Datastore。NDB 用戶端程式庫以舊版 DB Datastore 程式庫為基礎,但新增了以下資料儲存庫功能:

  • StructuredProperty 類別,可讓實體採用巢狀結構。
  • 經過整合的自動快取功能,通常可以透過內容快取和 Memcache 以成本低廉的方式快速讀取資料。
  • 支援同步 API 和用於並行操作的非同步 API。

本頁面提供 App Engine NDB 用戶端程式庫的簡介和總覽。如要瞭解如何遷移至支援 Python 3 的 Cloud NDB,請參閱「遷移至 Cloud NDB」。

定義實體、金鑰和屬性

Datastore 會儲存稱為「實體」的資料物件。實體具有一或多個「屬性」,這些屬性是幾種支援資料類型之一的命名值。例如,屬性可以是字串、整數,也可以是另一個實體的參照。

每個實體各有一個用於識別的「金鑰」,這是在應用程式儲存庫中不重複的 ID。鍵可以有父項,也就是另一個鍵。這個父項本身可能也有父項,以此類推;這個父項「鏈結」的頂端是沒有父項的鍵,稱為「根」

呈現實體群組中根實體與子系實體之間的關係

實體的金鑰若屬於同一個根,則會構成「實體群組」或「群組」。若實體屬於不同的群組,有時候對這些實體進行的變更會表現出「亂序」現象。若這些實體在您的應用程式語意中並不相關,就無所謂。但是,若其中一些實體的變更應該保持一致,則您的應用程式應在建立這些實體時將其歸為同一個群組。

下列實體關係圖和程式碼範例說明 Guestbook 如何擁有多個 Greetings,且每個 Greetings 都有 contentdate 屬性。

呈現使用隨附程式碼範例建立的實體關係

以下程式碼範例會應用這個關係。

import cgi
import textwrap
import urllib

from google.appengine.ext import ndb

import webapp2


class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

        greeting_blockquotes = []
        for greeting in greetings:
            greeting_blockquotes.append(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        self.response.out.write(textwrap.dedent("""\
            <html>
              <body>
                {blockquotes}
                <form action="/sign?{sign}" method="post">
                  <div>
                    <textarea name="content" rows="3" cols="60">
                    </textarea>
                  </div>
                  <div>
                    <input type="submit" value="Sign Guestbook">
                  </div>
                </form>
                <hr>
                <form>
                  Guestbook name:
                    <input value="{guestbook_name}" name="guestbook_name">
                    <input type="submit" value="switch">
                </form>
              </body>
            </html>""").format(
                blockquotes='\n'.join(greeting_blockquotes),
                sign=urllib.urlencode({'guestbook_name': guestbook_name}),
                guestbook_name=cgi.escape(guestbook_name)))


class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()
        self.redirect('/?' + urllib.urlencode(
            {'guestbook_name': guestbook_name}))


app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', SubmitForm)
])

使用模型儲存資料

模型是一種類別,用於說明實體的類型,包括其屬性的類型和設定。大致上類似於 SQL 中的資料表。您可以呼叫模型的類別建構函式來建立實體,然後再呼叫 put() 方法進行儲存。

以下程式碼範例會定義模型類別 Greeting。每個 Greeting 實體都有兩個屬性:問候語的文字內容和問候語建立日期。

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)
class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()

如要建立及儲存新的問候語,應用程式會建立新的 Greeting 物件,並呼叫其 put() 方法。

為確保留言板中的問候語不會顯示「不按順序排列」,應用程式會在建立新的 Greeting 時設定上層鍵。因此,新問候語會與同一個留言簿中的其他問候語位於同一個實體群組。應用程式進行查詢時會運用這項事實:會使用父項查詢。

查詢和索引

應用程式可以透過查詢功能尋找符合特定篩選條件的實體。

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

一般 NDB 查詢會依種類篩選實體。在本範例中,query_book 會產生查詢,傳回 Greeting 實體。查詢也可以指定實體屬性值和索引鍵的篩選條件。如同這個範例所示,查詢可以指定祖系,只找出「屬於」某個祖系的實體。查詢可以指定排序順序。針對篩選條件和排序順序中的每個屬性,若指定實體至少包含一個值 (有可能是空值),且其屬性值符合所有篩選準則,傳回的結果就會包含該實體。

每一次查詢均會使用一個「索引」,也就是一個資料表,表中會按照所需順序排序查詢結果。基礎 Datastore 會自動維護簡單索引 (只使用一種屬性的索引)。

在設定檔 index.yaml 中定義複合式索引。如果開發網頁伺服器遇到尚未設定索引的查詢,會自動在這個檔案中新增建議。

您可以在上傳應用程式之前編輯設定檔,以手動方式微調索引。如果您只要更新索引而不上傳應用程式,請執行 gcloud app deploy index.yaml。如果您的資料儲存庫包含許多實體,就需要較長的時間為實體建立新索引;在此情況下,最好先更新索引定義再上傳使用新索引的程式碼。您可以使用「管理控制台」確認完成建構索引的時間。

這種索引機制支援多種查詢,也適用於大多數的應用程式。但不支援其他資料庫技術常見的幾種查詢功能,尤其不支援彙整功能。

瞭解 NDB 寫入:提交、撤銷及套用快取

NDB 資料寫入步驟:

  • 在「提交」階段,基礎資料儲存庫服務會記錄變更。
  • NDB 會撤銷相關實體的快取內容。因此,在其後的讀取作業中,系統會讀取及快取基礎資料儲存庫中的資料,而非快取資料中過時的值。
  • 最後,在大約幾秒鐘之後,基礎資料儲存庫就會「套用」變更。基礎資料儲存庫會開放全域查詢和最終一致性讀取作業存取變更內容。

寫入資料的 NDB 函式 (例如 put()) 會在快取撤銷後傳回結果,「套用」階段並不會同步發生。

「提交」階段如出現失敗情形,會自動重試,但若持續失敗,應用程式就會出現例外狀況。若「提交」階段成功,但「套用」失敗,只要發生以下任一情形,就會將「套用」向前輪動至完成:

  • 定期執行的資料儲存庫清除作業會檢查未完成的「提交」工作,並予以套用。
  • 相關實體群組下一次執行寫入、交易或同步一致性讀取作業時,會在執行讀取、寫入或交易作業之前套用尚未套用的變更。

這個行為會影響應用程式取得資料的方式和時機。NDB 函式傳回後,可能會發生經過數百毫秒左右仍未將變更完全套用於基礎資料儲存庫的情形。在變更套用期間執行的非祖系查詢可能會看到不一致的狀態,也就是部分變更,但不是全部變更。

交易與快取資料

NDB 用戶端程式庫可以群組單項「交易」中的多項作業。交易中的每一項作業皆須成功,該項交易才會成功;若有任何一項作業失敗,交易就會自動復原。這項特性特別適合分散式網路應用程式,因為可能會有多位使用者同時存取或處理同一筆資料。

NDB 使用 Memcache 快取服務處理資料中的「作用點」。若應用程式經常讀取某些實體,NDB 可以快速自快取中讀取這些實體。

同時使用 Django 與 NDB

如要同時使用 NDB 與 Django 網路架構,請將 google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware 新增至 Django settings.py 檔案中的 MIDDLEWARE_CLASSES 項目。最好將它插入在任何其他中介軟體類別之前,因為可能會有某個其他中介軟體呼叫資料儲存庫,若先叫用該中介軟體再呼叫此中介軟體,可能無法正確處理這些中介軟體。您可以進一步瞭解 Django 中介軟體

相關資源

進一步瞭解: