Elasticsearchを使った簡単なタグ付けの実装

Elasticsearchを使用した簡単なタグ付けの実装

1. 概要

タグ付けは、データモデル内のアイテムを分類およびフィルタリングできるようにする一般的なデザインパターンです。

この記事では、SpringとElasticsearchを使用してタグ付けを実装します。 SpringDataとElasticsearchAPIの両方を使用します。

まず、ElasticsearchとSpring Dataの取得の基本については説明しません。これらのhereを調べることができます。

2. タグを追加する

The simplest implementation of tagging is an array of strings.次のようにデータモデルに新しいフィールドを追加することで、これを実装できます。

@Document(indexName = "blog", type = "article")
public class Article {

    // ...

    @Field(type = Keyword)
    private String[] tags;

    // ...
}

Keyword fieldタイプの使用に注意してください。 タグを完全に一致させて結果をフィルタリングするだけです。 これにより、elasticsearchIsAwesomeelasticsearchIsTerribleのような類似しているが別々のタグを使用できます。

分析されたフィールドは部分的なヒットを返しますが、この場合は間違った動作です。

3. クエリの作成

タグを使用すると、クエリを興味深い方法で操作できます。 他のフィールドと同じように検索することも、match_allクエリで結果をフィルタリングするために使用することもできます。 他のクエリでそれらを使用して、結果を絞り込むこともできます。

3.1. タグの検索

モデルに作成した新しいtagフィールドは、インデックス内の他のすべてのフィールドとまったく同じです。 次のような特定のタグを持つエンティティを検索できます。

@Query("{\"bool\": {\"must\": [{\"match\": {\"tags\": \"?0\"}}]}}")
Page
findByTagUsingDeclaredQuery(String tag, Pageable pageable);

この例では、Spring Data Repositoryを使用してクエリを作成していますが、Rest Templateを使用してElasticsearchクラスターに手動でクエリを実行することもできます。

同様に、Elasticsearch APIを使用できます。

boolQuery().must(termQuery("tags", "elasticsearch"));

インデックスで次のドキュメントを使用すると仮定します。

[
    {
        "id": 1,
        "title": "Spring Data Elasticsearch",
        "authors": [ { "name": "John Doe" }, { "name": "John Smith" } ],
        "tags": [ "elasticsearch", "spring data" ]
    },
    {
        "id": 2,
        "title": "Search engines",
        "authors": [ { "name": "John Doe" } ],
        "tags": [ "search engines", "tutorial" ]
    },
    {
        "id": 3,
        "title": "Second Article About Elasticsearch",
        "authors": [ { "name": "John Smith" } ],
        "tags": [ "elasticsearch", "spring data" ]
    },
    {
        "id": 4,
        "title": "Elasticsearch Tutorial",
        "authors": [ { "name": "John Doe" } ],
        "tags": [ "elasticsearch" ]
    },
]

これで、次のクエリを使用できます。

Page
articleByTags = articleService.findByTagUsingDeclaredQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

3.2. すべてのドキュメントのフィルタリング

一般的なデザインパターンは、すべてのエンティティを表示するUIにFiltered List Viewを作成することですが、ユーザーはさまざまな基準に基づいてフィルタリングすることもできます。

ユーザーが選択したタグでフィルタリングされたすべての記事を返したいとしましょう。

@Query("{\"bool\": {\"must\": " +
  "{\"match_all\": {}}, \"filter\": {\"term\": {\"tags\": \"?0\" }}}}")
Page
findByFilteredTagQuery(String tag, Pageable pageable);

ここでも、SpringDataを使用して宣言されたクエリを作成しています。

したがって、使用しているクエリは2つに分割されます。 スコアリングクエリは最初の用語であり、この場合はmatch_allです。 フィルタークエリは次にあり、Elasticsearchに破棄する結果を伝えます。

このクエリの使用方法は次のとおりです。

Page
articleByTags = articleService.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

上記の例と同じ結果が返されますが、このクエリのパフォーマンスは向上することを理解することが重要です。

3.3. クエリのフィルタリング

検索で返される結果が多すぎて使用できない場合があります。 その場合、結果を絞り込んで、同じ検索を再実行できるフィルタリングメカニズムを公開すると便利です。

これは、著者が書いた記事を特定のタグが付いた記事だけに絞り込む例です。

@Query("{\"bool\": {\"must\": " +
  "{\"match\": {\"authors.name\": \"?0\"}}, " +
  "\"filter\": {\"term\": {\"tags\": \"?1\" }}}}")
Page
findByAuthorsNameAndFilteredTagQuery( String name, String tag, Pageable pageable);

繰り返しますが、Spring Dataはすべての作業を行っています。

このクエリを自分で作成する方法も見てみましょう。

QueryBuilder builder = boolQuery().must(
  nestedQuery("authors", boolQuery().must(termQuery("authors.name", "doe")), ScoreMode.None))
  .filter(termQuery("tags", "elasticsearch"));

もちろん、この同じ手法を使用して、ドキュメント内の他のフィールドをフィルタリングできます。 ただし、タグはこのユースケースに特に適しています。

上記のクエリの使用方法は次のとおりです。

SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder)
  .build();
List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); // articles contains [ 1, 4 ] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(4))) );

4. フィルターコンテキスト

クエリを作成するとき、クエリコンテキストとフィルターコンテキストを区別する必要があります。 Elasticsearchのすべてのクエリにはクエリコンテキストがあるため、それらの表示に慣れる必要があります。

すべてのクエリタイプがフィルターコンテキストをサポートしているわけではありません。 したがって、タグでフィルタリングする場合は、使用できるクエリタイプを知る必要があります。

The bool query has two ways to access the Filter Context。 最初のパラメーターfilterは、上記で使用したパラメーターです。 must_notパラメータを使用してコンテキストをアクティブ化することもできます。

The next query type we can filter is constant_score。 これは、uuがクエリコンテキストをフィルターの結果で置き換え、各結果に同じスコアを割り当てる場合に役立ちます。

The final query type that we can filter based on tags is the filter aggregation。 これにより、フィルターの結果に基づいて集約グループを作成できます。 つまり、集計結果のタグごとにすべての記事をグループ化できます。

5. 高度なタグ付け

これまでのところ、最も基本的な実装を使用したタグ付けについてのみ説明しました。 次の論理的な手順は、それ自体がkey-value pairsであるタグを作成することです。 これにより、クエリとフィルターをさらに洗練させることができます。

たとえば、タグフィールドを次のように変更できます。

@Field(type = Nested)
private List tags;

次に、nestedQueryタイプを使用するようにフィルターを変更します。

key-value pairsの使用方法を理解したら、複雑なオブジェクトをタグとして使用するための小さなステップです。 タグとして完全なオブジェクトを必要とする実装は多くありませんが、必要に応じてこのオプションがあることを知っておくとよいでしょう。

6. 結論

この記事では、Elasticsearchを使用してタグ付けを実装するための基本について説明しました。

いつものように、例はover on GitHubで見つけることができます。