SyntaxHighlighter

2012年10月14日日曜日

Google App Engine Python NDB を使ってみた。(2)

【NDBエンティティとキー】

Datastoreに格納されているオブジェクト(エンティティ)はndb.Modelのインスタンスで、アプリケーションではndb.Modelのサブクラスとして定義する。
エンティティはkeyで識別され、アプリケーションのDatastore内でユニークとなる。

・概要


以下のようなエンティティクラスを定義する。
それぞれのエンティティはアプリケーション内のDatastoreで一意のキーで識別される。
キーがカインドと識別子を構成しているのが最も簡単なフォームである。
普通はカインドの名前はモデルクラス名と同じにする。上記の例ではAccount
だが、method _get_kind()を上書きすることで、名前を変える事も出来る。
識別子は、アプリケーションまたはデータストアで自動的に生成された整数の数字IDか割り当てられたいずれかのキー名の文字列である。
エンティティのキーは他のキーを親のキーとして示す事が出来る。これを”エンティティのキーの親”と呼ぶ。よくエンティティーの親とも呼ばれる。
コンテキストに依存することはエンティティのキーの親を意味する事か、エンティティはキーのキーを持っている事になる。ルートのエンティティーを持っていないエンティティはそれは親のエンティティで再起的にそれは先祖である。
エンティティはデータストアにいるが、階層の構成はファイルシステムの階層と同様である。エンティティの連続処理はルートのエンティティから始まって、親から子へと続く。
特定のエンティティにつながる、そのエンティティの祖先パスを構成している。

完全な一意キーは、つまり連続するカインド識別子を自身のエンティティーまで指定したその先祖キーである。
Keyクラスのコンストラクターは連続するカインドと識別子を指定すすことを許可している。その返却値のキーがそのエンティティに対応するキーとなる。
下記の例はメッセージのリビジョンを示すもので、rev_keyが所有者に所属している事になる。
上記例で注意が必要なのがリストの最後で2という数字を使っているがこれは特殊で数字キーを使うことはできるが、これは少しトリッキーになる。詳細は数値キーを使うを参照。

ルートエンティティ用としては下記のよう先祖パスは空で、自身のカインドと識別子のみで構成されている。
代わり以下のようにモデルクラスを直接代入できる。
下記例では指定しているキーはすべて等価である。

・エンティティの作成


モデルクラスのコンストラクターを呼び出すことでエンティティを作成する事が出来る。
プロパティの設定はkeyword argumentsで指定する。
ここで作成したオブジェクトはput()メソッドを呼びだす事によりデータストアへ格納さる。返却値はkeyとなっている。
代わりに以下のようにプロパティを直接指定できる。
以下のようにpopulate()を使うこともできる。

また、プロパティのタイプはいろいろなタイプが使えるがタイプチェックが行われる。
下記の例だと、StringPropertyとIntegerPropertyである。


・キーからエンティティの取得


エンティティーのキーを与えれば、データストアからエンティティを取得できる。
キーのメソッドであるkind()とidはそれぞれカインドと識別子を返却する。
parent()メソッドはキーの親のエンティティを返却する。
また、URLに埋め込むためにキーをエンコードした形で取得できる。
この生成方法ではagVoZWxsb3IPCxIHQWNjb3VudBiZiwIMのようなキーを返却するが、そこからエンティティも取得できる。

注意:URL-safeは暗号化されない。以下のように簡単に複合化できてしまうので注意。
なので、e-mailアドレスとかは暗号化したもので使用する。


・エンティティの更新


エンティティの更新はデータストアから取得したエンティティの編集を行い、データストアに戻す。
この場合のput()の返却値は同じなので無視してよい。


・エンティティの削除


エンティティが必要なくなったらデータストアから削除する。keyのdelete()メソッドで
削除できる。
このメソッドは常にNoneを返却する。


・複数のキー、エンティティを処理する。


get(),put()はRPC呼び出しを行っているので、ループを使って処理するしかなく非効率だったが、下記のメソッドで速くできる。


【Expandoモデル】


時々事前にプロパティを指定したくないときがある。その場合に特別なクラスExpandoを使う。Expandoは、割り当てられた任意の属性(限り、それはアンダースコアで始まらないように)がデータストアに保存されるように、そのエンティティの動作を変更する。たとえば、以下のように。
このデータストアへの書き込みはfooプロパティはInteger値で1barプロパティはString値'blah'でtagsプロパティはString値の繰り返しで'exp','and','oh'となっている。プロパティはインデックスされそれを_propertiesで参照することが出来る。
データストアから値を取得する事によって作成されたExpandoはデータストアに保存されたすべてのプロパティーとプロパティー値を持っている。
アプリケーションはExpandoのサブクラスとして定義して前もってプロパティを持つ事もできる。
上記の例ではnameプロパティはSandyでageはNone、動的プロパティlocationは'SF'となる。
Expandoのサブクラスに_default_indexed = Falseを指定することでインデックスから外すことができる。
_default_indexedをExpandoエンティティにセットすることができる。この場合は事後に指定したプロパティすべてに適用される。
他の便利なテクニックとしてはクエリに動的プロパティを使う事ができる。
ただ、プロパティを持っていない場合もあるので、下記のように指定する。


【モデルのフック】


NDBは軽量なフック機構を提供している。フックを利用することで、形式の処理を実現する事ができる。例としてはModelはget()のあとで様々な処理をするが、それを同期的にまたは非同期、複数など特別なメソッドを作れる。以下の例では、フックで様々な取得方法を提供している。
フックは下記で便利
・クエリーのキャッシュ
・ユーザー毎のデータストア監視
・データストアへのトリガー
もし非同期APIをフックとして使う場合はcheck_result()かget_result()かTaskletによるyieldingメソッドを呼びだすことがトリガーになる。
事後フックはRPCが成功していることは確認しない。失敗しても実行される。
すべての事後フックはFuture属性を持つ。このFutureオブジェクトは動作の結果を保持している。get_result()メソッドを呼びだせば、結果を取得できる。フックが呼び出された時点でFutureは完了しているので、get_result()でブロックされない事を確認できる。
事前フックで例外が発生すると、その場所をとってから、要求を防ぐことになる。
フックは_asyncメソッド内でトリガされるが、事前にRPCフックでtasklets.Returnを上げることによって、RPCを先取りすることはできない。


【数値キーを使う】


キーはカインドとIDの直列だが、アプリケーションとネームスペースないで、一意のキーを持っているか確認したくなる。アプリケーションがID指定なしでエンティティを作成すると、自動で数値キーが割り当てられる。アプリケーションが手動でID(数値の)を取り出すと、データストアは自動でIDを生成するが、既に使用されているIDを選択する場合がある。
回避するためには、アプリケーションが予約した数値の範囲を使用するようにするとよい。
(数値IDを使わなければ問題ない)
下記のように予約IDの範囲を指定できる。下記は100個のIDを割り当てている。
親キーと一緒に指定もできる。
キーの取り出し方は下記の通りで、first, lastの範囲でキーが割り当てられている。
これらのキーはデータストアの内部的なID生成で既に割り当てられていないIDである事は保証していない。また将来的に生成されるIDも同様である。 しかし、allocate_ids()が返すIDは、データストア内で存在するかどうかは確認しない。 代わりに下記のように最大値からID割り当てることもできる。 このフォームはN以下のIDを確実に返す。返却されるfirstとlastは予約されたIDの範囲を示す。 アプリケーションはトランザクション内でallocate_ids()を呼ぶことができない。 まあ、数値IDはあんまり使わない方が良さそう(感想)
次回は、プロパティについて。

2012年10月1日月曜日

Google App Engine Python NDB を使ってみた。(1)


今回は何回かにわたってGoogleAppEngineNDBという
DatastoreのAPIを紹介する。
というかリファレンスに載っているExampleとともに
自分の理解のために使ってみようと思う。

Python NDB の概要


NDB APIは通常のDatastoreのアクセスモジュールと使い方はほとんど変わらないが、
いくつかの便利な機能が追加されている。
自動でキャッシュを使ったり、クエリーやトランザクションなど構造化された
データレコードを格納する事に適している。

NDBの基本的な使い方


●始めに
NDBは通常のDatastoreのオブジェクト同様に、一つまたは複数のプロパティーを持つエンティティーとしてデータを格納する。
NDBは一つのトランザクションで複数の処理をまとめることが出来る。
もし処理が失敗したらロールバックされる。
これらは複数のユーザーが同時にアクセスしたり操作したりできるとても有用なものである。
NDBMemcacheサービスを使用してキャッシュを行う。
これは頻繁に同じエンティティーにアクセスする際に有用であり、高速で処理が行える。
NDBも通常のDatastoreと同様modelを定義する。
modelとはデータベースのスキーマのようなものである。
基礎となるDatastoreがこれらのデータオブジェクトを格納する方法は非常に柔軟である。
例えば、二つの異なるプロパティを持つエンティティーを同じカインドに格納する事ができる。NDBは型のチェックを行うが、それは必須ではない。
それぞれのエンティティはキーを持っていてそれらはアプリケーション内で一意である。
キーは親を持つ事ができて親子関係を作る事ができる。親の無いキーはルートと呼ぶ。
キーが同じルートを持つエンティティはエンティティグループまたはグループを形成することができる。
エンティティが別のグループに属している場合は、
これらのエンティティへの変更は、しばしば "順不同"が発生するように見えるかもしれない。
エンティティは、アプリケーションのセマンティクスとは無関係である場合、それは問題が、
それらを作成するときに、いくつかのエンティティの変更が一貫していなければならない場合、それらを同じグループの一部にする必要がある。

テストコードで実際に動作させてみた。

・データの格納と抽出


ここでは、カインドBookに一件エンティティ格納している。
格納した後にFetchを行って、抽出されたエンティティの検証を行っている。
Greetingオブジェクトのputメソッドを呼び出して格納している。
新たにGreetingオブジェクトを格納する場合は、すべて同じBookのエンティティグループ
となる。(親が同じ)すなわち、先祖クエリを使用している。
次にエンティティの抽出を行っている。
一般的にNDBのクエリーはカインドでエンティティーをフィルタする。
このサンプルではGreetingエンティティクラスにquery_bookというクラスメソッドを定義することにより、エンティティを返却する為のクエリーを生成している。
上記例では親のキーを指定することにより抽出するクエリーを実行している。
すべてのクエリは、インデックス、希望の順番でクエリの結果を含むテーブルを使用している。基礎となるDatastoreは、自動的にシンプルなインデックス(1つのプロパティのみを使用したインデックス)を維持する。
またこれらはindex.yamlで定義したインデックスも使用できる。

実行結果

NDBのデータ書き込みについて
コミット段階では、基礎となるDatastoreサービスでは、変更をコミットする。
NDBは影響を受けるエンティティ/エンティティのそのキャッシュを無効にする。
したがって将来の(およびキャッシュ)からではなく、キャッシュから古い値を読んでの基礎となるデータストアを読み込む。
そして基礎となるデータストアは、変更を適用する。それがグローバルクエリには表示され、最終的には一貫性の変化を読み取ることができる。(結果整合性)

データ(例えば、put()の)書き込みのNDB関数はキャッシュ無効化した後に返し、適用は非同期で行われる。

Commitフェーズ中に障害が発生した場合、自動再試行がありますが、障害が引き続き発生する場合は、アプリケーションが例外を受け取る。
コミット段階が成功して適用に障害が発生した場合、次のいずれかが発生したときに、適用フェーズは最後までロールフォワードされる。

・データストアは適用が不完全に終わったジョブを継続的に一掃し、エンティティへの変更をまだ受け取っていないインデックスとエンティティに対して、書き込みをロール フォワードします。
・次回このエンティティ グループでトランザクションを書き込むか、トランザクションを開始する際に、データストアは最初にロール フォワードを行い、ログ内のデータに基づいて、このコミット済みで未適用の書き込みの適用を完了します。

このような振る舞いはアプリケーションにどのように影響するかというと、
変化は完全に数百ミリ秒かそこらのNDB関数が戻った後に、基礎となるデータストアに適用されない場合がある。(結果整合性)
つまり変更が適用されている間にクエリを発行すると矛盾した状態が表示される場合がありる。書き込みのタイミングとクエリの詳細については、アプリケーションEngine.readsのトランザクション分離を参照とのこと。

●Django
DjangoでもNDBは使える。

●QuotaとLimits
最大エンティティーサイズ:1 megabyte
最大トランザクションサイズ:10 megabytes
1つのエンティティへの最大インデックス数:20000
1つのエンティティへの複合インデックス数:2 megabytes

今回は、NDBの概要について紹介してみた。
次回はNDBのエンティティとKeyについて紹介する予定。