Global Transaction


ご存知の通り、Local Transactionつまりcom.google.appengine.api.datastore.Transactionは複数の entity groups にまたがる処理を行えません。そのため複数Entity Groupを整合性を保って更新するのはとても大変です。

でも安心してください。Slim3では複数Entity Groupにまたがる Global Transaction をサポートしています。

Global Transactionが必要になるような一般的な使い方は"Bank Account"のサンプルでしょう。あなたは銀行で、幾つかの口座を持っているとします。
定義は以下のようになります:

Account

@Model
public class Account {

    @Attribute(primaryKey = true)
    private Key key;

    private Integer balance;
    ...
}

口座間で送金できるようにする必要が普通ありますよね。それぞれの送金処理はトランザクションで処理されるべきであり、さもなければ預金を失ってしまったり、(銀行の立場から見れば)悪いことに預金残高が倍に増えてしまいます。
それぞれのアカウントは競合を避けるため異なったEntity Groupにアサインされているので、Local Transactionでお金を送金することは不可能です。

幸いなことに、以下のようにGlobal Transactionを使えばこのようなことも可能になります。
public void tansferFunds(Key srcKey, Key destKey, Integer amount) {
GlobalTransaction gtx = Datastore.beginGlobalTransaction();
Acount src = gtx.get(Acount.class, srcKey);
Acount dest = gtx.get(Acount.class, destKey);
if (src.getBalance() >= amount) {
src.setBalance(src.getBalance() - amount);
dest.setBalance(dest.getBalance() + amount);
} else {
throw new IllegalStateException(...);
}
gtx.put(src, dest);
gtx.commit();
}

簡単ですね。

Global Transactionの基本的なステップは次のようになります:
  1. Get/Put/Delete時にEntity Groupに対してロックを行い、誰も更新できないようにします。
  2. ロールフォワードするためにJournalを書き込みます。JournalはPut/Delete情報を持ちます。
  3. 対象のEnityにJournalを適用します。
  4. Entity Groupのロックを解放します。
初期化のために、一番最初のEntity GroupはLocal Transaction内で実行します。もしGlobal Transactionを1つのEntity Groupに対して使用した時のパフォーマンスはLocal Transactionのときと変わりません。

同一のEntity Groupに対してGlobal TransactionとLocal Transactionを混ぜて使わないでください。Local Transactionでの変更内容を失ってしまうかもしれません。
Local Transactionの代わりにGlobal Transactionを常に使用することをお勧めします。


Global Transactionを使うためには以下のように設定する必要があります:

WEB-INF/web.xml

<servlet>
    <servlet-name>
        GlobalTransactionServlet
    </servlet-name>

    <servlet-class>
        org.slim3.datastore.GlobalTransactionServlet
    </servlet-class>

    <load-on-startup>1</load-on-startup>
</servlet>
  
<servlet-mapping>
    <servlet-name>
        GlobalTransactionServlet
    </servlet-name>

    <url-pattern>/slim3/gtx</url-pattern>
</servlet-mapping>

<security-constraint>
    <web-resource-collection>
        <url-pattern>/slim3/gtx/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
</security-constraint>

WEB-INF/queue.xml

<queue-entries>
    <queue>
        <name>slim3-gtx-queue</name>
        <rate>10/s</rate>
        <bucket-size>10</bucket-size>
    </queue>
</queue-entries>

グローバルトランザクションのオーバーヘッドが気になるかもしれませんが、心配しないでください。そんなに高くありません。
デモを用意しました。

ローカルトランザクションのテストコード

long start = System.currentTimeMillis();
for (int i = 0; i < entityGroups; i++) {
   Transaction tx = Datastore.beginTransaction();
   Datastore.put(tx, new Entity("Hoge"));
   tx.commit();
}
long time = System.currentTimeMillis() - start;

グローバルトランザクションのテストコード

start = System.currentTimeMillis();
GlobalTransaction gtx = Datastore.
beginGlobalTransaction();
for (int i = 0; i < entityGroups; i++) {
   gtx.put(new Entity("Hoge"));
}
gtx.commit();
time = System.currentTimeMillis() - start;

結果:
エンティティグループ数ローカルトランザクション[ms]グローバルトランザクション[ms]
16770
2450415
3213539
41498981
5447781

次のURLでデモを実行することができます。


次のソースファイルを参考にしてください:

次は...

リレーションシップ に続く

Comments