Atom Login Admin

Above the clouds

Google App Engine Datastore #3 ~ Datastoreへの書き込み ~

written on Sunday,February 10,2013

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

Login
This entry was tagged #GAE #Datastore