2016-08-28 7 views
1

私は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()); 
    } 
} 

答えて

3

インフィニスパンにはインデックス機能があります。 「Infinispan Query」を探します。

個人的に私はキャッシュ内のインデックス作成の大きなファンではありません。ベンダー間での機能は非常に異なります。あなたのような問題については、私はインデックス作成を行う正当な理由は見当たりません。私はすべてのキャッシュで動作する最も簡単なソリューションを試してみることをお勧めします。

解決方法1:

、それはsubFoo IDへのfooのIDからインデックス本質的である別のキャッシュを追加します。Cache<Long, List<Long>>

このキャッシュはIDのみを記憶しているので、のない重複はありませんデータ。

解決方法2:

親エンティティのキ​​ャッシュにsubFoo IDを追加します。

class CachedFoo { 
    Foo foo; 
    List<Long> subFooIds; 
} 

Cache<Long, CachedFoo> fooCache; 

解決方法3:ケースで

あなたはそんなにいないデータを持っており、 findSubFooがめったに使用されない場合は、プロセスキャッシュ内の代替品であるCache<Long, List<SubFoo>を考慮してください。インプロセスキャッシュにはデータ値ではなくオブジェクト参照が格納されるため、冗長性はありません。

+0

ありがとうございました。私はTreeCacheを使う前に、ソリューション1からアイディアを実装しようとしました。しかし、私はトランザクション間の一貫性を保証しませんでした。 2つの並行トランザクションTx1とTx2が同じキーに対してCache >からList を変更するとします。 Tx1は新しい値を追加し、Tx2はリストから値を削除します。最終的な結果は、最後にコミットされた取引からのリストです(Tx1になることもあれば、Tx2になることもあります)。これを避けるには、PESSIMICTICロックモードを使用する必要があります。 – ostry

+0

TreeCacheがどのように解決策であるかわかりません。一般的には、キャッシュを直接変更する場合、一貫性を確保したい場合は、適切なトランザクション/ロックを行う必要があります。もう1つのアプローチは、突然変異の際にキャッシュを無効にして、データベースから読み取って常にキャッシュにデータを入れることです。 Additionaly、ここで私の発言を参照してください:http://stackoverflow.com/questions/25969983/guava-cache-how-to-block-access-while-doing-removal/26011908#26011908 – cruftex

+0

私は、そのTreeCacheは何かこの場合の回避策のもちろん、並行性の問題は依然として残っていました。特に、Txの1つがノードを削除し、2つ目のadd要素をリストに追加すると、この問題の解決策は、悲観的なロック・モードでした。すべてがうまくいくように見えました。残念なことに、多数のリクエストが矛盾していました。私はTx後の変更を承認すると思います、それぞれのキャッシュは独立して実行されました。 – ostry