2009年12月18日金曜日

#appengine でTransactionを使った保存とそうでない保存

#appengine java night #3( #ajn3 )に参加したのエントリで書いた以下の動作を確認しました。

ルートエンティティが存在しなくてもエンティティグループに対するトランザクション処理が正常に行われる。つまり、トランザクション管理で使用されるエンティティグループのタイムスタンプは、Datastoreのエンティティとは関係が無いところで管理されている可能性がある

これについて、@marblejenkaさんが詳細に書いてくれています。紹介した記事の主題はmakeAsyncCall()をフックしてProtocolBufferのレベルで詳細表示するロガーの件ですが、その動作サンプルの題材にこの記事の冒頭で書いた「ルートエンティティが存在しない場合のTransactionの動作」、が登場します。まぁこれは自分が@marblejenkaさんにおねだりしたってのもあるからかもしれないけど。

その中で、appengineのdatastoreの動作についてわかりやすい証明がされている箇所があったのでいくつか抜粋してみます。

親エンティティが存在しない子エンティティをTrasactionを使わずに保存

Javaで実行したコードにたいして、実際にサービスが呼び出された場合のサービスとアプリケーション間のRequest/Responseを紹介する形で説明していきます。いくつかの要素に関しては改行をとっぱらって表記しています。

service.allocateIds("parent", 1); // 親エンティティ用のallocateIds
request ===================
datastore_v3:AllocateIds(
  model_key <
    app: "balmysundaycandy"
    path <
      Element { type: "parent" name: "ignored" }
    >
  >
  size: 1
);

response ===================
start: 15007
end: 15007
これは特に目新しい事はありません。
service.allocateIds(parentKey, "child", 1); // 子エンティティ用のallocateIds
request ===================
datastore_v3:AllocateIds(
  model_key <
    app: "balmysundaycandy"
    path <
      Element { type: "parent" id: 15007 }
      Element { type: "child" name: "ignored" }
    >
  >
size: 1
);
response ===================
start: 1
end: 1
親エンティティのKeyをサービスに渡していることが見えます。
Entity child = new Entity(childKey); service.put(child); // 子エンティティを保存
request ===================
datastore_v3:Put(
  entity <
    key <
      app: "balmysundaycandy"
      path <
        Element { type: "parent" id: 15007 }
        Element { type: "child" id: 1 }
      >
    >
    entity_group <
      Element { type: "parent" id: 15007 }
    >
  >
);
response ===================
key <
  app: "balmysundaycandy"
  path <
    Element { type: "parent" id: 15007 }
    Element { type: "child" id: 1 }
  >
>
cost <
  index_writes: 1
  index_write_bytes: 1
  entity_writes: 1
  entity_write_bytes: 318
>
ここが重要!

最後の子エンティティのPUT時のリクエストにentity_groupという要素が登場しています。API的には隠蔽されたentity_groupという要素が存在することで、親エンティティなしでもEntityGroupも扱えるっていう仕組みになっているんですね。当然のことながらこのentity_group要素は親エンティティのKeyを保持しています。この例だとTransactionは使ってませんが、この時点で「ルートエンティティが存在しない場合のTransactionの動作」の謎が解けましたね。

ここから先は「ルートエンティティが存在しない場合のTransactionの動作」には関係がない余談となります。別の視点で面白い事も見えたので、続けてTransactionを使った場合のサービスとアプリケーション間の通信の詳細も紹介します。視点を「Transactionを使わないPUT、Transactionを使うPUTの動作の差」に切り替えましょう。上の例で注目すべきはPUT時の通信のレスポンスにあるcost/index_writesです。

Transactionを使った場合のPUT

Transaction tx = service.beginTransaction();
request ===================
datastore_v3:BeginTransaction();
response ===================
handle: 0xe99cea1e678995af
TransactionのIDがレスポンスに含まれています。
service.put(tx, child);
request ===================
datastore_v3:Put(
  entity <
    key <
      app: "balmysundaycandy"
      path <
        Element { type: "parent" id: 16006 }
        Element { type: "child" id: 1 }
      >
    >
    entity_group <
      Element { type: "parent" id: 16006 }
    >
  >
  transaction <
    handle: 0xe99cea1e678995af
  >
);
response ===================
key <
  app: "balmysundaycandy"
  path <
    Element { type: "parent" id: 16006 }
    Element { type: "child" id: 1 }
  >
>
cost <
  index_writes: 0
  index_write_bytes: 0
  entity_writes: 0
  entity_write_bytes: 0
>
先のTransactionを使わない場合に比べて、リクエストにTransactionのIDが含まれているのが見えます。そしてもうひとつ、cost/index_writes1ではなく0になっています。
tx.commit();
request ===================
datastore_v3:Commit(
  handle: 0xe99cea1e678995af
);
response ===================
cost <
  index_writes: 1
  index_write_bytes: 1
  entity_writes: 1
  entity_write_bytes: 318
>
commitをしたときにcost/index_writes1となっています。

Transactionを使う場合と使わない場合のインデックスの作成のタイミングが違う(Transactionを使用しない場合はPut時にインデックスの作成が実行され、Transaction使用時はCommit時にインデックスの作成が実行される)という仕様について、実際に見ることができました。というお話でした。@marblejenkaさんありがとう、まだ垢BANされてなかったんですね!

コメントを投稿