AppEngineでJDO2.3を使用しています。私はローカルテストにマスター/スレーブデータストアを使用していましたが、最近ローカルテストにHRDデータストアを使用するように切り替えましたが、アプリの一部が壊れています(これは予想されることです)。壊れているアプリの一部は、大量の書き込みをすばやく送信する場所です。つまり、1秒の制限があるため、同時変更の例外で失敗します。
さて、それも予想されることなので、後で失敗したときにブラウザに書き込みを再試行させます(おそらく最高のハックではありませんが、すぐに機能させようとしています)。
しかし、奇妙なことが起こっています。コミットフェーズが完了し、リクエストが成功コードを返したとしても、成功するはずの書き込みの一部(同時変更例外を受け取らないもの)も失敗します。ログから、再試行されたリクエストは正常に機能していることがわかりますが、最初の試行でコミットされたように見えるこれらの他のリクエストは、「適用」されていないようです。しかし、私が適用フェーズについて読んだことから、同じエンティティに再度書き込むと、適用が強制されるはずです...しかし、そうではありません。
コードは次のとおりです。注意すべき点:
- 自動JDOキャッシングを使用しようとしています。したがって、これはJDOが内部でmemcacheを使用する場所です。すべてをトランザクションでラップしない限り、これは実際には機能しません。
- すべてのリクエストは、エンティティから文字列を読み取り、文字列の一部を変更し、その文字列をエンティティに保存することです。これらのリクエストがトランザクションに含まれていなかった場合は、もちろん「ダーティリード」の問題が発生します。しかし、トランザクションでは、分離は「シリアル化可能」のレベルであると想定されているため、ここで何が起こっているのかわかりません。
- 変更されるエンティティはルートエンティティです(グループ内ではありません)
- グループ間取引を有効にしました
関連するコード(これは簡略化されたバージョンです):
PersistenceManager pm = PMF.getManager();
Transaction tx = pm.currentTransaction();
String responsetext = "";
try {
tx.begin();
// I have extra calls to "makePersistent" because I found that relying
// on pm.close didn't always write the objects to cache, maybe that
// was only a DataNucleus 1.x issue though
Key userkey = obtainUserKeyFromCookie();
User u = pm.getObjectById(User.class, userkey);
pm.makePersistent(u); // to make sure it gets cached for next time
Key mapkey = obtainMapKeyFromQueryString();
// this is NOT a java.util.Map, just FYI
Map currentmap = pm.getObjectById(Map.class, mapkey);
Text mapData = currentmap.getMapData(); // mapData is JSON stored in the entity
Text newMapData = parseModifyAndReturn(mapData); // transform the map
currentmap.setMapData(newMapData); // mutate the Map object
pm.makePersistent(currentmap); // make sure to persist so there is a cache hit
tx.commit();
responsetext = "OK";
} catch (JDOCanRetryException jdoe) {
// log jdoe
responsetext = "RETRY";
} catch (Exception e) {
// log e
responsetext = "ERROR";
} finally {
if (tx.isActive()) {
tx.rollback();
}
pm.close();
}
resp.getWriter().println(responsetext);
更新:なぜこれが起こっているのかは確かにわかっていますが、それを確認できる人には賞金を授与します。
基本的に、問題はトランザクションが実際にはローカルバージョンのデータストアに実装されていないことだと思います。参照:
https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java/gVMS1dFSpcU https://groups.google.com/forum/?fromgroups=#!topic/google-appengine-java / deGasFdIO-M https://groups.google.com/forum/?hl=en&fromgroups=#!msg/google-appengine-java/4YuNb6TVD6I/gSttMmHYwo0J
トランザクションは実装されていないため、ロールバックは基本的にノーオペレーションです。したがって、2つのトランザクションが同時にレコードを変更しようとすると、ダーティリードが発生します。つまり、Aがデータを読み取り、Bが同時にデータを読み取ります。Aはデータを変更しようとし、Bはデータの別の部分を変更しようとします。Aがデータストアに書き込み、次にBが書き込み、Aの変更を消去します。次に、Bはアプリエンジンによって「ロールバック」されますが、ローカルデータストアで実行している場合、ロールバックはノーオペレーションであるため、Bの変更は保持され、Aは保持されません。一方、Bは例外をスローしたスレッドであるため、クライアントはBを再試行しますが、Aは再試行しません(Aは成功したトランザクションであると考えられるため)。