私はWildfly 10.0.0サーバ上で動作するアプリケーションを持っています。エンティティをデータベースに保存しますが、パフォーマンスを向上させるために、私はInfinispanキャッシュ(キャッシュアサイドパターン)を使用することに決めました。 アプリケーションのパフォーマンスは増加したが、まだ計算関係親に問題がありました - 子供(fooとSubFoos):Infinispanの親子関係のキャッシュのようなデータベースインデックス
これは、エンティティコードです:
@Entity
class Foo
{
Long id;
}
@Entity
class SubFoo
{
Long id;
Long fooId;
}
これは、サービスコードです:
public class FooService
{
@Inject
EntityManger em;
@Inject
Cache<Long, Foo> fooCache;
@Inject
Cache<Long, SubFoo> subFooCache;
public void save(Foo foo)
{
em.save(foo);
fooCache.put(foo.id, foo);
}
public void save(SubFoo subFoo)
{
em.save(subFoo);
subFooCache.put(subFoo.id, subFoo);
}
public void remove ....
public void update ....
public Foo get(Long fooId)
{
return fooCache.get(fooId);
}
public SubFoo get(Long subFooId)
{
return subFooCache.get(subFooId);
}
public List<SubFoo> findSubFoo(Long fooId)
{
return subFooCache.values().stream().filter(subFoo -> subFoo.fooId == fooId).collect(Collector.list());
}
}
問題はfindSubFooメソッドです。毎回、すべてのsubFoosコレクションをチェックする必要があります。また、この方法はアプリケーションのパフォーマンスに大きな影響を与えます。
データベース索引の使用をシミュレートするか、そうでない場合に問題を解決するのは不可能ですか?
アプローチ1
は私がキャッシュ値としてリストを格納し、同時実行性とトランザクション・サポートを維持するためのTreeCacheを使用しようとしました。 treeNodeはFooIdをノードのルートパスとして、subFooIdをリーフとして保持します。 このアプローチは、要求の数が少ない場合は問題ありませんでした。一貫性のない状態では、一貫性の欠如による短期間の要求が多く発生します。 Txがコミットされ、1つのキャッシュ(エンティティsubFooの標準)がリフレッシュされたように見えましたが、2番目(foo2subFoo)はまだありません。しかし、短期間のうちにすべてが正常であり、データの一貫性が回復しました。
ソースコード:生産者と
キャッシュプロバイダ:
@ApplicationScoped
public class CacheProvider
{
private EmbeddedCacheManager cacheManager;
@PostConstruct
public void init()
{
final GlobalConfiguration globalConfig =
new GlobalConfigurationBuilder().nonClusteredDefault().globalJmxStatistics()
.allowDuplicateDomains(true).build();
final Configuration entityDefaultConfig =
new ConfigurationBuilder().transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.lockingMode(LockingMode.OPTIMISTIC)
.eviction().strategy(EvictionStrategy.NONE).build();
final Configuration indexDefaultConfig = new ConfigurationBuilder()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.eviction().strategy(EvictionStrategy.NONE)
.invocationBatching().enable()
.build();
cacheManager = new DefaultCacheManager(globalConfig );
cacheManager.defineConfiguration("Foos", entityDefaultConfig);
cacheManager.defineConfiguration("SubFoos", entityDefaultConfig);
cacheManager.defineConfiguration("Foo2SubFoos", indexDefaultConfig);
}
@Produces
public Cache<Long, Foo> createFooCache()
{
final Cache<Long, Foo> entityCache = cacheManager.getCache("Foos");
return entityCache;
}
@Produces
public Cache<Long, SubFoo> createSubFooCache()
{
final Cache<Long, SubFoo> entityCache = cacheManager.getCache("SubFoos");
return entityCache;
}
@Produces
public TreeCache<Long, Boolean> createFoo2SubFoos()
{
Cache<Long, Boolean> cache = cacheManager.getCache("Foo2SubFoos");
final TreeCacheFactory treeCacheFactory = new TreeCacheFactory();
final TreeCache<Long, Boolean> treeCache = treeCacheFactory.createTreeCache(cache);
return treeCache;
}
}
と私はのTreeCacheのサポートと私のFooServiceを拡張:subFoo IDを追加すると、削除または削除foo2SubFooキャッシュがあまりにも更新されます。
public class FooService
{
@Inject
EntityManger em;
@Inject
Cache<Long, Foo> fooCache;
@Inject
Cache<Long, SubFoo> subFooCache;
@Inject
TreeCache<Long, Boolean> foo2SubFoosCache;
public void save(Foo foo)
{
em.save(foo);
fooCache.put(foo.id, foo);
}
public void save(SubFoo subFoo)
{
em.save(subFoo);
subFooCache.put(subFoo.id, subFoo);
Fqn fqn = Fqn.fromElements(subFoo.fooId);
foo2SubFoosCache.put(fqn, subFoo,id, Boolean.TRUE);
}
public void remove ....
public void update ....
public Foo get(Long fooId)
{
return fooCache.get(fooId);
}
public SubFoo get(Long subFooId)
{
return subFooCache.get(subFooId);
}
public List<SubFoo> findSubFoo(Long fooId)
{
Fqn fqn = Fqn.fromElements(fooId);
return foo2SubFoosCache.getKeys(fooId).stream().map(subFooId -> subFooCache.get(subFooId)).collect(Collector.list());
}
}
ありがとうございました。私はTreeCacheを使う前に、ソリューション1からアイディアを実装しようとしました。しかし、私はトランザクション間の一貫性を保証しませんでした。 2つの並行トランザクションTx1とTx2が同じキーに対してCache>からList を変更するとします。 Tx1は新しい値を追加し、Tx2はリストから値を削除します。最終的な結果は、最後にコミットされた取引からのリストです(Tx1になることもあれば、Tx2になることもあります)。これを避けるには、PESSIMICTICロックモードを使用する必要があります。 –
ostry
TreeCacheがどのように解決策であるかわかりません。一般的には、キャッシュを直接変更する場合、一貫性を確保したい場合は、適切なトランザクション/ロックを行う必要があります。もう1つのアプローチは、突然変異の際にキャッシュを無効にして、データベースから読み取って常にキャッシュにデータを入れることです。 Additionaly、ここで私の発言を参照してください:http://stackoverflow.com/questions/25969983/guava-cache-how-to-block-access-while-doing-removal/26011908#26011908 – cruftex
私は、そのTreeCacheは何かこの場合の回避策のもちろん、並行性の問題は依然として残っていました。特に、Txの1つがノードを削除し、2つ目のadd要素をリストに追加すると、この問題の解決策は、悲観的なロック・モードでした。すべてがうまくいくように見えました。残念なことに、多数のリクエストが矛盾していました。私はTx後の変更を承認すると思います、それぞれのキャッシュは独立して実行されました。 – ostry