SiNBLOG

140文字に入らないことを、極稀に書くBlog

Slim3の楽観的排他制御

明日のSlim3 Source Code Reading #3 に向けて、ちょっとやってみた。

楽観的排他制御ですが、ひが殿のBlogに詳細が書いてあります。
App Engineでバージョンによる楽観的排他制御 - yvsu pron. yas

ただ、ソースが古くなっているので、公式ドキュメントを見た方が良さそうです。
Optimistic Locking with version property - Slim3


そして、僕が書いてみたソースはこちら。


public Tweet updateContent(Key key, Long version, String content) {
Transaction tx = Datastore.beginTransaction();
try {
Tweet store = Datastore.get(t, key, version);
store.setContent(content);
Datastore.put(store);
tx.commit();
return store;
} catch (ConcurrentModificationException e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
}
}

公式チュートリアルの呟きアプリに更新部分を入れてみました。
中身は、ほぼ公式ドキュメントのままです。


それでは、中で何をしているのかを見ていきましょう。
Datastore.get(t, key, version)の中を潜っていくと、実際の処理は以下のようです。


public Future getAsync(Transaction tx, final ModelMeta modelMeta,
final Key key, final Long version) throws NullPointerException,
IllegalStateException {
if (version == null) {
throw new NullPointerException(
"The version parameter must not be null.");
}
return new FutureWrapper(getAsync(tx, key)) {

@Override
protected Throwable convertException(Throwable throwable) {
return throwable;
}

@Override
protected M wrap(Entity entity) throws Exception {
ModelMeta mm = DatastoreUtil.getModelMeta(modelMeta, entity);
mm.validateKey(key);
M model = mm.entityToModel(entity);
mm.postGet(model);
if (version != mm.getVersion(model)) {
throw new ConcurrentModificationException(
"Failed optimistic lock by key("
+ key
+ ") and version("
+ version
+ ").");
}
return model;
}
};
}

ここの判定でversionを比較し、一致しない場合は例外を投げます。
GAEのDBであるBigtableでは、Where句を付けて特定のデータを更新!ができないので、
先にgetして比較しているわけですね。


次はDatastore.put(store)の中に潜って行きます。
そうすると、以下のソースがあります。


public static Entity modelToEntity(AsyncDatastoreService ds, Object model)
throws NullPointerException {
if (ds == null) {
throw new NullPointerException("The ds parameter must not be null.");
}
if (model == null) {
throw new NullPointerException(
"The model parameter must not be null.");
}
ModelMeta modelMeta = getModelMeta(model.getClass());
Key key = modelMeta.getKey(model);
if (key == null) {
key = allocateId(ds, modelMeta.getKind());
modelMeta.setKey(model, key);
}
modelMeta.assignKeyToModelRefIfNecessary(ds, model);
modelMeta.incrementVersion(model);
modelMeta.prePut(model);
return modelMeta.modelToEntity(model);
}

modelMeta.incrementVersion(model)の中で、versionをインクリメントしています。
modelMetaは自動生成されるクラスです。
僕が作ったTweetModelでは以下のようになっています。


@Override
protected void incrementVersion(Object model) {
org.sinsandbox.model.Tweet m = (org.sinsandbox.model.Tweet) model;
long version = m.getVersion() != null ? m.getVersion().longValue() : 0L;
m.setVersion(Long.valueOf(version + 1L));
}

このメソッドはput時に必ず呼ばれるため、楽観的排他制御を行なっていなくてもインクリメントされます。


これで、明日を乗りきれるかな?