TransactionとEntity Groupについての理解
ドキュメントや各所のblog、TwitterのTLなどを眺めていると、TransactionとEntity Groupについての理解で混乱することがあったので注記しておく。production環境のDatastoreで動作を確認するまで確信できなかった。
TransactionとEntity Groupについて一般的に言われること
- トランザクション内ではひとつのEntity Group内のEntityしか更新できない
- 異なるEntity GroupのEntityは別のTransactionで処理してね
- Entityふたつの間で一貫性が欲しければ同じEntity Groupにするしかないよ
- Entity Groupを大きくしすぎるとパフォーマンスが落ちる
- Entity Groupが同じデータはDatastore上でのマスタノードが共通になるので、そこに負荷が集中する
- オフィシャルのドキュメントを読むと「Entity Groupはひとり分のデータのサイズにおさえておくのがよい」とある
あれ? じゃあ新しいEntityを追加するときは?
あるEntityを追加するとき、例えば顧客名を key_name にセットした以下のようなEntityを考えてみる。
class UserAccount(db.Model): fullname = db.StringProperty() user1 = UserAccount(key_name="tagomoris", fullname="Tagomori Satoshi") user1.put()
何も考えなければこれでいい。
さて、この "tagomoris" というUserAccountが追加されようとした瞬間に、同時に以下のようなコードが実行されようとしている可能性を考えてみる。
user2 = UserAccount(key_name="tagomoris", fullname="Tagomori SSSSSSS") user2.put()
何も制御が行われていなければ両方のコードが成功し、そして前にput()されたデータは失われる。
で、そういった状況を回避するためにTransactionの登場だ。
でも、あれ? 待てよ。Transactionは「同じEntity GroupのEntityしか扱えない」んじゃなかったっけ? ここで user1 と user2 は両方とも parent 属性をセットしていない(root entity)。ってことはEntity Groupも別だから、次みたいにTransactionを書いてもうまくいかないんじゃないの?
def add_useraccount(name, fullname): u = UserAccount.get_by_key_name(name) if u is not None: raise UserAccountNameAlreadyInUse u = UserAccount(key_name=name, fullname=fullname) u.put() return u new_user = db.run_in_transaction(add_useraccount, "tagomoris", "Tagomori Satoshi")
結論から書くと、このコードは完璧に正しく期待通りに動作する。そしてこのコードを経由して作成した各Entityは、きちんと別々のEntity Groupに所属することになる。
手で衝突を起こしてみたときに表示される以下の警告から、その理由がわかる。
WARNING:root:Transaction collision for entity group with key datastore_types.Key.from_path(u'UserAccount', u'tagomoris', _app_id_namespace=u'application_id')
つまりTransaction内では、(多分Keyに関する操作がある場合には)暗黙のうちに「Key.from_path(u'UserAccount', u'tagomoris')を親とするEntity Groupに対する操作」として扱われている、ということだろう。なので、特定の named key (key_nameで指定されたKey)を用いたput()処理はTransaction内で正常に衝突検出できている、ということだ。
この暗黙の関係はKeyに関係する操作がある場合(つまりおそらくput()とdelete()がある場合。delete()はphantom readになるだけなので違うかも?)のみなので、通常のEntity操作ではきちんと別のEntity Groupとして扱われ、並列分散処理が可能となる。
ま、アレだ。新しいEntityのinsert時だけ気をつけようぜ、ってことで。