私は AppEngine プロジェクトに取り組んでおり、永続化のために AppEngine データストアの上で JDO を使用しています。エンコードされた文字列をキーとして使用し、アプリケーションで生成されたキー名 (文字列) も使用するエンティティがあります。これを行ったのは、私のアプリが頻繁に野生からデータをスクープし (同じものをスクープする可能性があります)、それらを保持しようとするからです。基本的に同じデータを含む複数のエンティティを永続化することを避けるために、これらのデータに関するいくつかのプロパティをハッシュして、一貫したキー名を取得することにしました (エンティティの関係のためにキーを直接操作するのではありません)。ここでの問題は、ハッシュ (キー名) を計算してエンティティを格納しようとするたびに、エンティティがデータストアに既に存在する場合、データストア (または JDO などの犯人) は、例外を発生させることなく、データストア内のエンティティのプロパティを黙って上書きします。これは、エンティティ (順序付けに使用する) の timeStamps (フィールド) をオーバーライドするため、アプリに深刻な影響を与えます。これを回避するにはどうすればよいですか?
3 に答える
get-before-set (Check and set または CAS) を実行する必要があります。
CAS は同時実行の基本的なテナントであり、並列コンピューティングの必要悪です。
いずれにせよ、Gets は Sets よりもはるかに安いので、実際にお金を節約できます。
データストアへのブラインド書き込みの代わりに、最初に取得します。エンティティが存在しない場合は、例外をキャッチしてエンティティを配置します。存在する場合は、保存する前に詳細な比較を行います。何も変更されていない場合は、永続化しないでください (そしてそのコストを節約します)。変更された場合は、マージ戦略を自由に選択してください。日付付きのリビジョンを維持する 1 つの (やや醜い) 方法は、以前のエンティティを更新されたエンティティのフィールドとして保存することです (多くのリビジョンでは機能しない可能性があります)。
ただし、この場合、セット前に取得する必要があります。多くの重複が予想されず、本当に安っぽくしたい場合は、最初にexistsクエリを実行できます...これは、使用したいキーに対してキーのみのカウントクエリを実行することです(コストは完全な取得よりも7倍少なくなります) )。If (count() == 0) then put() else getAndMaybePut() fi
カウント クエリ構文は遅く見えるかもしれませんが、私のベンチマークでは、エンティティが存在するかどうかを確認する最速 (かつ安価) な方法です。
public boolean exists(Key key){
Query q;
if (key.getParent() == null)
q = new Query(key.getKind());
else
q = new Query(key.getKind(), key.getParent());
q.setKeysOnly();
q.setFilter(new FilterPredicate(
Entity.KEY_RESERVED_PROPERTY, FilterOperator.EQUAL, key));
return 1 == DatastoreServiceFactory.getDatastoreService().prepare(q)
.countEntities(FetchOptions.Builder.withLimit(1));
}
新しいエンティティを put() する前に、 get() を実行して、同じキーを持つエンティティが存在するかどうかを確認する必要があります。これを回避する方法はありません。
memcache とローカルの「メモリ内」キャッシュを使用して、get() 操作を高速化できます。これは、同じ情報を何度も読む可能性がある場合にのみ役立ちます。そうでない場合、memcache クエリによって実際にプロセスが遅くなる可能性があります。
2 つのリクエストが互いに上書きしないようにするには、トランザクションを使用する必要があります (更新を 1 秒あたり 1 に制限する可能性がある単一のエンティティ グループにすべてのアイテムを配置しない限り、Ajax が提案するクエリでは不可能です)。
擬似コード:
- ハッシュデータからキーを作成
- キーのメモリ内キャッシュをチェックし (キーの ConcurrentHashSet を使用)、見つかった場合は返します
- キーの MemcacheService を確認し、見つかった場合は返す
- 取引開始
- データストアからエンティティを取得し、見つかった場合は返す
- データストアにエンティティを作成
- トランザクションをコミットし、同時更新が原因で失敗した場合は返す
- キーをキャッシュに入れる (メモリ内および memcache)
別の要求 (スレッド) が同じキーを同時に書き込んでいる場合、ステップ 7 は失敗します。
ID を文字列として保存する代わりに、エンティティにロング ID を使用するか、appengine によって自動生成される Key データ型を使用することをお勧めします。
@PersistenceCapable
public class Test{
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long ID;
// getter and setter
}
これにより、毎回一意の値が返されます。