Datastore へのデータの保存


Python ゲストブックのコードのこの部分では、構造化データを Datastore に保存する方法を説明します。App Engine と Datastore では、データの分散、レプリケーション、ロード バランシングについて心配する必要はありません。単純な API で実現されます。加えて、強力なクエリエンジンとトランザクションも使用できます。

このページは複数ページからなるチュートリアルの一部です。設定などの手順を最初から見るには、ゲストブックの作成に移動してください。

送信されたメッセージの保存

データは、Datastore 内のエンティティと呼ばれるオブジェクトに書き込まれます。各エンティティには、それを一意に識別するキーが割り当てられます。エンティティでは、別のエンティティを「親」として指定することもできます。最初のエンティティは、親エンティティの「子」になります。こうして、データストア内のエンティティは、ファイル システムのディレクトリ構造と同じような階層構造空間を形成します。詳細については、強整合性に対応するデータ構造をご覧ください。

App Engine には Python 用のデータ モデリング API が付属しています。データ モデリング API を使用するために、サンプルアプリは google.appengine.ext.ndb モジュールをインポートします。各メッセージには投稿者の名前、メッセージの内容、投稿日時が含まれています。アプリではメッセージが時系列で表示されます。次のコードは、データモデルを定義します。

class Author(ndb.Model):
    """Sub model for representing an author."""
    identity = ndb.StringProperty(indexed=False)
    email = ndb.StringProperty(indexed=False)

class Greeting(ndb.Model):
    """A main model for representing an individual Guestbook entry."""
    author = ndb.StructuredProperty(Author)
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

このコードは、次の 3 つのプロパティで Greeting モデルを定義します。値がメールアドレスと投稿者の ID を含む Author オブジェクトである author、値が文字列である content、値が datetime.datetime である date です。

パラメータで詳細な動作を構成できるプロパティ コンストラクタもあります。ndb.StringProperty コンストラクタに indexed=False パラメータを渡すと、このプロパティの値にインデックスが付けられません。これにより、アプリでそのプロパティがクエリに使用されなくなるため、不要な書き込みをしなくて済みます。ndb.DateTimeProperty コンストラクタに auto_now_add=True パラメータを渡すと、アプリケーションで値が入力されない場合に、自動的に新しいオブジェクトに作成時の datetime スタンプが付与されるようにモデルが構成されます。プロパティ タイプとそれらのオプションの完全なリストについては、NDB プロパティをご覧ください。

アプリケーションでは、新しい Greeting オブジェクトを作成して Datastore に配置するためにデータモデルが使用されます。Guestbook ハンドラが新しいメッセージを作成してそれらをデータストアに保存します。

class Guestbook(webapp2.RequestHandler):

    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each
        # Greeting is in the same entity group. Queries across the
        # single entity group will be consistent. However, the write
        # rate to a single entity group should be limited to
        # ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = Author(
                    identity=users.get_current_user().user_id(),
                    email=users.get_current_user().email())

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))

この Guestbook ハンドラは、新しい Greeting オブジェクトを作成してから、その author プロパティと content プロパティをユーザーが投稿したデータに設定します。Greeting の親は Guestbook エンティティです。別のエンティティの親に設定する前に、Guestbook エンティティを作成する必要はありません。この例では、トランザクションと整合性を目的としたプレースホルダとして親を使用します。詳細については、トランザクション ページをご覧ください。共通の祖先を共有するオブジェクトは、同じエンティティ グループに属しています。このコードでは date プロパティが設定されないため、date は自動的に auto_now_add=True を使用して現在に設定されます。

最後に、greeting.put() によって新しいオブジェクトがデータストアに保存されます。このオブジェクトをクエリから取得した場合は、put() によって既存のオブジェクトが更新されます。このオブジェクトはモデル コンストラクタを使用して作成されているため、put() によって新しいオブジェクトがデータストアに追加されます。

Datastore におけるクエリはエンティティ グループ内でのみ強整合性を示すため、コードでは、すべてのメッセージに共通する親を設定することにより、1 つのブック内のすべてのメッセージが同じエンティティ グループに割り当てられます。したがって、メッセージは常に書き込みの直後にユーザーに表示されます。ただし、同じエンティティ グループに書き込める頻度は毎秒 1 回に制限されます。実際のアプリケーションを設計するときは、この点を考慮する必要があります。Memcache などのサービスを使用すると、書き込み後に複数エンティティ グループを横断してクエリを実行した場合に、ユーザーに新しい結果が表示されなくなる可能性を低減できます。

送信されたメッセージの取得

Datastore は、データモデル用の高度なクエリエンジンを備えています。Datastore は従来型のリレーショナル データベースではないため、クエリを指定する際に SQL は使用しません。その代わりに、Datastore クエリを使用するか、GQL という SQL に似たクエリ言語を使用して、データのクエリを行います。Datastore のすべてのクエリ機能を活用するには、GQL によるクエリを使用することをおすすめします。

MainPage ハンドラは、過去に送信されたメッセージを取得して表示します。greetings_query.fetch(10) を呼び出すとクエリが実行されます。

Datastore インデックスの詳細

Datastore 内のすべてのクエリが 1 つ以上のインデックスから計算されます。このインデックスは、順序付けられたプロパティ値をエンティティ キーにマップするテーブルです。アプリケーションのデータストアの規模にかかわらず App Engine がすばやく結果を返すことができるのは、この仕組みによります。多くのクエリは組み込みのインデックスから計算できますが、より複雑なクエリの場合は、Datastore にカスタム インデックスが必要になります。カスタム インデックスを使用しない場合は、Datastore でこれらのクエリを効率的に実行できません。

たとえば、ゲストブック アプリケーションは、上位クエリと並べ替え順序を使用して、ゲストブックでフィルタリングし、日付順に並べ替えます。そのためには、アプリケーションの index.yaml ファイルでカスタム インデックスを指定する必要があります。このファイルは、手動で編集することも、アプリケーションでローカルにクエリを実行することで自動的に処理することもできます。index.yaml でインデックスを定義してから、アプリケーションをデプロイすると、カスタム インデックス情報もデプロイされます。

index.yaml 内のクエリの定義は次のようになります。

indexes:
- kind: Greeting
  ancestor: yes
  properties:
  - name: date
    direction: desc

Datastore インデックスの詳細については、データストア インデックス ページをご覧ください。index.yaml ファイルの正しい指定方法については、Python データストアのインデックスの構成をご覧ください。