5

これは、この質問で概説したプロジェクトの続きです。

私は次のモデルを持っています:

class Product {
  public string Id { get; set; }
  public string[] Specs { get; set; }
  public int CategoryId { get; set; }
}

「Specs」配列は、特殊文字で結合された製品仕様名と値のペアを格納します。たとえば、商品が青色の場合、スペック文字列は「Color〜Blue」になります。このように仕様を表すと、クエリで指定された複数の仕様値を持つ製品をクエリできます。私がサポートしたい2つの主要なクエリがあります:

  1. 特定のカテゴリのすべての製品を取得します。
  2. 指定された仕様のセットを持つ特定のカテゴリのすべての製品を取得します。

これはRavenDBでうまく機能します。ただし、特定のクエリを満たす製品に加えて、クエリで指定された製品のセットのすべての仕様の名前と値のペアを含む結果セットを返したいと思います。仕様の名前と値のペアは、仕様の名前と値でグループ化する必要があり、特定の仕様の名前と値のペアを持つ製品の数を含める必要があります。クエリ#1では、次のマップリデュースインデックスを作成しました。

class CategorySpecGroups {
    public int CategoryId { get; set; }
    public string Spec { get; set; }
    public int Count { get; set; }
}


public class SpecGroups_ByCategoryId : AbstractIndexCreationTask<Product, CategorySpecGroups>
{
    public SpecGroups_ByCategoryId()
    {
        this.Map = products => from product in products
                               where product.Specs != null
                               from spec in product.Specs
                               select new
                               {
                                   CategoryId = product.CategoryId,
                                   Spec = spec,
                                   Count = 1
                               };

        this.Reduce = results => from result in results
                                 group result by new { result.CategoryId, result.Spec } into g
                                 select new
                                 {
                                     CategoryId = g.Key.CategoryId,
                                     Spec = g.Key.Spec,
                                     Count = g.Sum(x => x.Count)
                                 };
    }
}

次に、このインデックスをクエリして、特定のカテゴリのすべての仕様名と値のペアを取得できます。私が遭遇している問題は、同じ結果セットを取得することですが、カテゴリと仕様の名前と値のペアのセットの両方でフィルタリングするクエリの場合です。SQLを使用する場合、この結果セットは、カテゴリと仕様でフィルタリングされた製品のセットに対してgroupbyを実行することによって取得されます。一般に、このタイプのクエリはコストがかかりますが、カテゴリと仕様の両方でフィルタリングする場合、製品セットは通常は小さくなりますが、1つのページに収まるほど小さくはありません。最大1000個の製品が含まれる場合があります。参考までに、MongoDBは、同じ結果セットを実現するために使用できるグループメソッドをサポートしています。これにより、アドホックグループ化サーバー側が実行され、パフォーマンスは許容範囲内です。

RavenDBを使用してこのタイプの結果セットを取得するにはどうすればよいですか?

考えられる解決策の1つは、クエリのすべての製品を取得してメモリ内でグループ化を実行することです。別のオプションは、上記のようにmapreduceインデックスを作成することですが、これに伴う課題は、特定のカテゴリに対して実行できるすべての可能な仕様の選択を推測することです。さらに、このタイプのインデックスはサイズが爆発する可能性があります。

例として、このファスナーカテゴリページを見てください。ユーザーは、属性を選択することで選択をフィルタリングできます。属性を選択すると、製品の選択が絞り込まれ、新しい製品セット内の属性が表示されます。このタイプのインタラクションは通常、ファセット検索と呼ばれます。

編集

それまでの間、 Solrを使用して解決策を試みます。これは、ファセット検索をすぐにサポートするためです。

編集2

RavenDBはファセット検索もサポートしているようです(もちろん、これは理にかなっています。インデックスはSolrと同じようにLuceneによって保存されます)。私はこれを調査し、更新を投稿します。

編集3

RavenDBファセット検索機能は期待どおりに機能します。特定のカテゴリ内のクエリのファセットを計算するために使用される各カテゴリIDのファセット設定ドキュメントを保存します。私が今抱えている問題はパフォーマンスです。4500の異なるカテゴリを持つ500kの製品のコレクションの場合、4500のファセット設定ドキュメントが生成されます。カテゴリIDによるクエリは、ファセットをクエリする場合は約16秒、ファセットをクエリしない場合は約0.05秒かかります。テストされた特定のカテゴリには、約6kの製品、23の異なるファセット、および2kの異なるファセットの名前と範囲の組み合わせが含まれています。FacetedQueryRunnerのコードを見た後ファセットクエリは、カウントを取得するためのすべてのファセット名と値の組み合わせに対するLuceneクエリ、および用語を取得するための各ファセット名に対するクエリをもたらすようです。実装の問題の1つは、クエリに関係なく、特定のファセット名のすべての個別の用語を取得することです。これにより、ほとんどの場合、ファセットの用語の数が大幅に減り、Luceneクエリの数が減ります。ここでパフォーマンスを向上させる1つの方法は、ファセット設定ドキュメントごとにMapReduceの計算結果セット(上記のとおり)を保存し、ファセットでさらにフィルタリングするときにすべての個別の用語を取得するためにクエリを実行することです。ただし、全体的なパフォーマンスはまだ遅すぎる可能性があります。

4

1 に答える 1

3

RavenDBファセット検索を使用してこの機能を実装しましたが、ヒューリスティック最適化をサポートするためにFacetedQueryRunnerにいくつかの変更を加えました。ヒューリスティックは、私の場合、ファセットはリーフカテゴリでのみ表示されるというものです。ルートカテゴリと内部カテゴリ間のナビゲーションは、子カテゴリの検索またはリストのいずれかによって駆動できるため、これは妥当な制約です。

ここで、制約が与えられた場合、各リーフカテゴリのFacetSetupドキュメントを保存します。IDは「facets/category_123」のようなものです。ファセット設定ドキュメントが保存されているとき、カテゴリに含まれているファセット名とファセット値(または範囲)にアクセスできます。したがって、FacetSetupドキュメントの各ファセットのRangesコレクションに使用可能なすべてのファセット値を保存できますが、ファセットモードはFacetMode.Defaultのままです。

FacetedQueryRunnerへの変更は次のとおりです。具体的には、最適化は特定のファセットが範囲を格納しているかどうかを確認します。その場合、最適化は、特定のファセットに関連付けられたインデックス内のすべての用語を取得する代わりに、検索に使用する値を返します。ほとんどの場合、特定のカテゴリで使用可能なファセット値はインデックス全体のファセット値のサブセットであるため、これにより、必要なLucene検索の数が大幅に削減されます。

実行できる次の最適化は、元のクエリがカテゴリIDでのみフィルタリングされる場合、FacetSetupドキュメントは実際にカウントも保存できることです。これを行う方法の1つは、ハッキーではありますが、Rangesコレクションの各ファセット値にカウントを追加してから、FacetSetupドキュメントにブール値を追加してカウントが追加されることを示すことです。これで、このファセットクエリは基本的にFacetSetupドキュメントの値を返します-クエリする必要はありません。

ここで考慮すべきことは、FacetSetupドキュメントを最新の状態に保つことですが、どちらの方法でもこれが必要になります。この最適化を超えて、キャッシングを利用できます。これは、Solrファセット検索によって採用されたアプローチであると私は信じています。

さらに、FacetSetupドキュメントが製品コレクションと自動的に同期されると、効果的には、最初にカテゴリID、次にファセットの名前、次に値でグループ化された製品のセットに対するMapReduce操作の集約の結果であるため便利です。

于 2011-10-11T19:25:29.100 に答える