web-dev-qa-db-ja.com

ドメインオブジェクトをキャッシュする方法を理解するための最良の方法を理解しようとしています

私はいつもこれを間違っていました。他の多くの人も持っていると思います。マップを介して参照を保持し、DBなどに書き込みます。

私はこれを正しく行う必要がありますが、どうすればいいのか分かりません。私は自分のオブジェクトをどのようにキャッシュしたいのかを知っていますが、どのようにそれを達成するのかわかりません。複雑なのは、アプリケーションに通知することなくDBを変更できるレガシーシステムでこれを行う必要があることです。

したがって、Webアプリケーションのコンテキストで、いくつかのメソッドを持つWidgetServiceがあるとします。

_Widget getWidget();
Collection<Widget> getAllWidgets();
Collection<Widget> getWidgetsByCategory(String categoryCode);
Collection<Widget> getWidgetsByContainer(Integer parentContainer);
Collection<Widget> getWidgetsByStatus(String status);
_

これを考えると、メソッドシグネチャによってキャッシュすることを決定できます。つまり、getWidgetsByCategory("AA")は単一のキャッシュエントリを持つか、ウィジェットを個別にキャッシュすることができますが、これは難しいと思います。または、任意のメソッドを呼び出すと、最初にgetAllWidgets()を呼び出してすべてのウィジェットがキャッシュされますが、getAllWidgets()は、他のメソッド呼び出しのすべてのキーと一致するキャッシュを生成します。たとえば、次のテストされていない理論上のコードを見てください。

_Collection<Widget> getAllWidgets() {
    Entity entity = cache.get("ALL_WIDGETS");
    Collection<Widget> res;
    if (entity == null) {
        res = loadCache();
    } else {
        res = (Collection<Widget>) entity.getValue();
    }
    return res
}

Collection<Widget> loadCache() {
    // Get widgets from underlying DB
    Collection<Widget> res = db.getAllWidgets();
    cache.put("ALL_WIDGETS", res);
    Map<String, List<Widget>> byCat = new HashMap<>();
    for (Widget w : res) {
        // cache by different types of method calls, i.e. by category
        if (!byCat.containsKey(widget.getCategory()) {
            byCat.put(widget.getCategory(), new ArrayList<Widget>);
        }
        byCat.get(widget.getCatgory(), widget);
    }
    cacheCategories(byCat);
    return res;
}

Collection<Widget> getWidgetsByCategory(String categoryCode) {
    CategoryCacheKey key = new CategoryCacheKey(categoryCode);
    Entity ent = cache.get(key);
    if (entity == null) {
        loadCache();
    }
    ent = cache.get(key);
    return ent == null ? Collections.emptyList() : (Collection<Widget>)ent.getValue();
}
_

[〜#〜] note [〜#〜]:キャッシュマネージャーを使用していません。上記のコードはcacheを、キーと値のペアでキャッシュを保持できるオブジェクトとして示しています、ただし特定の実装をモデルにしたものではありません。

これを使用すると、ヒープ上の単一のオブジェクトのみで呼び出されるさまざまな方法ですべてのオブジェクトをキャッシュできるという利点がありますが、たとえば、Springを介してメソッド呼び出しの呼び出しをキャッシュすると、複数キャッシュされますオブジェクトのコピー。

間違ったパスをたどり、後で自分自身を困難にする前に、ドメインオブジェクトをキャッシュするための最良の方法を試して理解したいのです。私はEhcacheのWebサイトのドキュメントを読み、興味のあるさまざまな記事を見つけましたが、優れた確かな手法を提供するものは何もありません。

ERPシステムで作業しているため、DB呼び出しが非常に複雑であり、DBが遅いわけではありませんが、ドメインオブジェクトのビジネス表現により、非常に扱いにくく、このアプリケーションが単一のビューに統合している情報を含むことができる11の異なるDBが実際にあること、これはキャッシングを非常に重要にします。

4
Brett Ryan

キャッシュを有効にするには、データベースから取得するよりも速くデータにアクセスできる必要があります。ほとんどのデータベース呼び出しがネットワークのラウンドトリップを伴うことを考えると、出力(値)が既知のディメンション(時間、サイズなど)で同じ入力(キー)に対して変化しないことがわかっているときはいつでもキャッシュするのが理にかなっています。

したがって、入力が予測できない、または解読できないため、単一のキーにバインドできない場合、キャッシングは最良のソリューションではない可能性があります。質の低いデータベースを作成(および維持)しようとするだけです。代わりに、より高速なネットワークにお金を投資してください。

キーがダウンストリームコンシューマに役立つ限り、キャッシュに特定のオブジェクトの複数のコピーが含まれているかどうかは、実際には問題になりません。キャッシュの目的は、さまざまな観点(たとえば、顧客中心または請求中心)から基礎となるデータセットにアクセスする可能性があるデータのさまざまな利用者のパフォーマンスを向上させることです。

小規模での実装に関しては、EhCacheではなく Guavaライブラリのキャッシュクラス を確認することをお勧めします。自己生成キャッシュの典型的な例は次のとおりです。

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

ご覧のとおり、Guavaでの作業は非常に簡単であり、さまざまなキャッシュエビクション戦略(サイズ、参照、経過時間など)を提供しています。 Guavaライブラリーには、JDKで見つかったものを補強する便利なユーティリティクラスも豊富に用意されているため、コードベース全体にメリットがあります。

したがって、専用キャッシュとDAO結果セットを派生キーで組み合わせるクラスでDAOメソッドを装飾するアプローチは適切です。メソッドを呼び出すたびに、返される前に初期ローカルキー検索が行われます。これが、探しているように見えます。これを各方法に合わせて調整された適切な立ち退き戦略と組み合わせると、適切にスケーリングする必要があるソリューションを理解し、維持するのが簡単になります。

2
Gary Rowe