Datastoreへの書き込みについて
今回は、Datastoreへの書き込みについて内部で行われている処理を理解する。
まず、下記のようなModelオブジェクトを定義する。
class ToDo(db.Model):
owner = db.User(auto_current_user_add=True)
created = db.DateTimeProperty(auto_now_add=True)
priority = db.IntegerProperty()
description = db.TextProperty()
due = db.DateProperty()
データストアに追加する場合は
put()を下記のように呼び出す。
my_todo = ToDo(priority=5, description='Fix that leaky faucet',
due=datetime.date(2009, 8, 7))
my_todo.put()
put()の呼び出しが戻るとエンティティのキーが設定される。
put()の裏側で何が行われているかというと、
1.my_todo オブジェクトはプロトコル バッファに変換されます。
2.アプリケーション サーバーでデータストア サーバーに対する RPC 呼び出しを行い、プロトコル バッファでエンティティ データを送信します。
3.キー名が提供されていない場合は、このエンティティのキーに対して一意の ID が決定されます。エンティティ キーは app ID | ancestor keys | kind name | key name or ID の構成になります。
4.データストア サーバーは、コミット、適用の順に実行される 2 つのフェーズで要求を処理します。各フェーズで、データストア サーバーは、データを受け取る Bigtable タブレット サーバーを識別します。
プロトコルバッファからRPC呼び出し
まず、1,2のプロトコルバッファからRPC呼び出しについて。
この記事XMLはもう不要!? Google製シリアライズツール「Protocol Buffer」で紹介されているが、
Protocol Bufferは、一言で言うと、構造化データをバイト列に変換(シリアライズ)するソフト>ウェアである。プログラム言語中で用いられるデータ構造をファイルに保存する際や、
RPC(Remote Procedure Call)でデータをやり取りする際などに用いられる。
Google App Engineではどのようになっているのかと、ソースを追っていく。
まずput()を呼び出すと(putは同期的に行われるが、内部ではput_asyncを呼び出して、get_result()している)google.appengine.api.datastoreのPutが呼び出される。
続いて、google.appengine.datastore.datastore_rpcのasync_put()でRPCリクエストされる。
ここでPutRequestというProtocol Bufferが生成されるのだが、
entity <
key <
app: "testapp"
path <
Element {
type: "ToDo"
id: 0
}
>
>
entity_group <
>
property <
meaning: 7
name: "created"
value <
int64Value: 0x4d550cbdf74e0
>
multiple: false
>
property <
meaning: 7
name: "due"
value <
int64Value: 0x47081ead8a000
>
multiple: false
>
property <
name: "priority"
value <
int64Value: 5
>
multiple: false
>
raw_property <
meaning: 15
name: "description"
value <
stringValue: "Fix that leaky faucet"
>
multiple: false
>
>
文字列で表すと上記のようなエンティティのデータが設定されているようだ。
実際には各サービスによって変わるシリアライズされたデータでRPCコールを行う。
また、DatastoreのFunctionにmodel_to_protobuf()があるので試してみるのもいいかも。
エンティティのキー
3.のエンティティのキーについてだが、アプリケーションで明示的に設定する事もできる。
下記に簡単な動作の確認を行うテストをやってみた。
def testStoringData(self):
my_todo = ToDo()
my_todo.put()
# allocate for MyModel without an instance
handmade_key = db.Key.from_path('ToDo', 1)
first_batch = db.allocate_ids(handmade_key, 10)
first_range = range(first_batch[0], first_batch[1] + 1)
# or allocate using an existing key
model_instance = ToDo.all().get()
second_batch = db.allocate_ids(model_instance.key(), 10)
second_range = range(second_batch[0], second_batch[1] + 1)
# and then use them! woo!
my_id = second_range.pop(0)
new_key = db.Key.from_path('ToDo', my_id)
new_instance = ToDo(key=new_key)
new_instance.put()
self.assertEquals(my_id, new_instance.key().id())
# the Datastore will not assign ids in first_batch or second_batch
another_instance = ToDo()
another_instance.put()
self.assertTrue(another_instance.key().id() not in first_range)
self.assertTrue(another_instance.key().id() not in second_range)
まず、IDに1を設定したキーを作成する。IDには任意の文字列を設定することもできる。次にキーからIDを10個割り当てる。
db.allocate_idsの返却値はタプルの(1,10)となる。
既存のエンティティのキーからIDを10個割り当てる。
割り当てたIDからエンティティを作成する。そのエンティティのIDは割り当てたIDであることを確認する。
最後に新たにエンティティを作成し(IDは自動生成)、そのキーは既に割り当てられているIDでないことを確認している。
任意のIDを設定することができるが、このようにしてallocate_ids()やallocate_ids_async()を使ってDatastoreの自動採番IDで競合を起こすことを避ける事が出来る。
任意で指定した数値IDとデータストアで自動採番されたIDとで競合が起きると、
BadRequestError: the id allocated for a new entity was already in use, please try again
とエラーになる。(実験してみたのだが、本番環境だと起こらなかった。ただ競合する可能性はあるらしい。)
Datastoreへの格納
4.データストアサーバーがデータを格納する際に、コミットフェーズと適用フェーズの2つのフェーズで要求を処理する。
1つ目がコミットフェーズで、エンティティグループのログにエンティティのデータを書き込み、新しいログ エントリをコミット済みとしてマークする。
2つ目が適用フェーズで、エンティティのデータとインデックス行が並行してディスクに書き込まれる。エンティティで設定されているプロパティによっては、多数のインデックスエントリの追加が必要になる場合がある。データストアは、設定ファイルでアプリケーション用に定義された複合インデックスと、すべてのアプリケーションで自動的に提供されるインデックスで、合致するインデックスをチェックする。
AppEngineでは計6つのBigtableを使用して、エンティティとインデックスの格納を行っている。
-
Entities table
エンティティーのキー、プロパティ、エンティティのメタデータ、カスタムインデックスが含まれている。 -
Index tables
EntitiesByKind, EntitiesByProperty ASC, EntitiesByProperty DESC, EntitiesByCompositePropertyの4つのテーブルから構成されている。 -
Custom Index table
カスタムインデックスの構成を保持している。 -
Id sequences table
IDシーケンスはエンティティとカスタムインデックス両方の数値データストアIDを生成する為にしようされる。
上記の6つのBigtableに格納することとなる。
詳しくはHow Entities and Indexes are Storedを参照。
次回は、Datastoreのトランザクションについていろいろ調べてみる。
Comments
Add Comment