UUUM攻殻機動隊

UUUMのエンジニアによる技術ブログです

Elasticsearchの基本的な使い方と弊社サービスでの利用

UUUMに入って2か月のkitabatakeです。 任される仕事が多くなってきて、面白くなってきたと思う一方、ちゃんと捌ききれるかどうかのせめぎ合いの日々です。

弊社のシステムでも使っている、Elasticsearch の基本的な使い方と、弊社内での利用方法など書いてみたいと思います。

Elasticsearchとは?

全文検索エンジン

大量にあるドキュメントデータの中から、目的のワードを含むドキュメントデータを検索する。 検索するということに特化したミドルウェア

RDBMSとの違い

「RDBMS」は、検索条件にマッチするデータ取得できる。

Elasticsearchのような「全文検索エンジン」は、「検索条件との関係性/関連性が高いデータを抽出して返す」という特徴がある。

RDBMSにも全文検索機能が搭載されているものもあるが、検索機能に最適化されている分、パフォーマンスや機能性は一般的に「全文検索エンジン」の方が優れている。

データの登録から関連性の紐付けについての概要

下記資料のサザエさんのところにかなり解りやすく説明されています

ElasticSearch入門 // Speaker Deck

実際に試してみる

弊社のクリエイターポータルサイト「CREAS」では素材の検索にElasticsearchを使っています。 同じようなケースの想定で、解りやすくミニマムのデータ構造で試してみます。 Elasticsearchがインストール済みの想定で進めます。

簡易素材データ

  • ID
  • name
  • description

Indexの作成

Indexとは?

データを保存する場所。 RDBMSのdatabaseのようなもの

indexの一覧を確認するコマンド

curl -XGET 'localhost:9200/_aliases?pretty'

---

{}

?pretty はレスポンスのjsonを見やすく表示させるオプションです。 まだ何もしていないので、空のレスポンスが返ってきます。

indexを作成します。今回はindex名を material_test とします。

curl -XPOST 'http://localhost:9200/material_test'

でindex一覧を確認

curl -XGET 'localhost:9200/_aliases?pretty'
---
{
  "material_test" : {
    "aliases" : { }
  }
}

作れました。

Mapping

外部データをElasticsearch上でどのようなスキーマとして表現するか定義することをmappingと呼びます。 (Elasticsearchチュートリアル - 不可視点より。参考: Mapping のイメージ)

マッピングの確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : { }
  }
}

Elasticsearchはデータを投入すると、自動的にマッピングを作成してくれるので、まずはそれを試してみます。 データを投入します。

curl -XPUT 'localhost:9200/material_test/material/1' -d '{
    "title" : "アラビアンジュエル",
    "description" : "アラビアンナイトな雰囲気の怪しい楽曲です。 ゲームなどの、砂漠の街のイメージなどにおすすめです。"
}'

で、Mappingの確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : {
      "material" : {
        "properties" : {
          "description" : {
            "type" : "string"
          },
          "title" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

name, descriptionのtypeがstringと定義されました。 これでデータが検索できる状態になったので、 WEB UIから検索等のできるプラグイン

github.com

を使って試してみよう。

インストール後に、 http://localhost:9200/_plugin/inquisitor/#/queries にアクセスします。

Queriesタブで検索クエリーの発行、Analyzersタブで文章の解析結果が確認できます。

Queries画面

f:id:sissoko:20161122090137p:plain

Analyzers画面

f:id:sissoko:20161122090157p:plain

自動的に生成されたmappingでは日本語をうまく解析できていないですね。

いい感じに検索できるようにする

日本語対応 analyzer kuromoji(http://www.atilika.org/) というものがあり、これを設定することで、日本語の検索がいい感じになります。

mappingを手動で設定するコマンド

curl -XPUT 'localhost:9200/material_test' -d '
{
  "settings": {
    "analysis": {
      "filter": {
        "pos_filter": {type: "kuromoji_part_of_speech", stoptags: ["助詞-格助詞-一般", "助詞-終助詞"]},
        "greek_lowercase_filter": {type: "lowercase", language: "greek"}
      },
      "tokenizer": {
        "kuromoji": {
          "type": "kuromoji_tokenizer"
        },
        "ngram_tokenizer": {
          "type": "nGram",
          "min_gram": "2",
          "max_gram": "3",
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      },
      "analyzer": {
        "kuromoji_analyzer": {
          "type": "custom",
          "tokenizer": "kuromoji_tokenizer",
          "filter": [
            "kuromoji_baseform",
            "pos_filter",
            "greek_lowercase_filter",
            "cjk_width"
          ]
        },
        "ngram_analyzer": {
          "tokenizer": "ngram_tokenizer"
        }
      }
    }
  },
  "mappings" : {
    "material" : {
      "properties" : {
        "name" : {
          "type" : "string",
          "analyzer": "kuromoji_analyzer"
        },
        "description" : {
          "type" : "string",
          "analyzer": "kuromoji_analyzer"
        }
      }
    }
  }
}'

急に複雑になってしまったので、細かく説明していきます。

まず Analyzerとは?

Analyzer = Tokenizer + Filter です。

Tokenizer?

文字を分割するもの

  • ngram_tokenizer:

    最小と最大の文字と対象にする文字の種類を設定し、分割する

  • kuromoji_tokenizer:

    日本語に対応したプラグインのtokenizer

Filter?

分割した文字を整形したり、フィルタリングするもの

  • kuromoji_baseform

    動詞と形容詞を原型に戻す。

  • pos_filter

    特定の品詞を除外し、マッチ精度を高める

  • greek_lowercase_filter

    英字を小文字化する

  • cjk_width

    全角英数字を半角に直し、半角カタカナを全角に直すなど、漢字圏の文字の全半角を整え、マッチしやすくする

設定を確認する

Analyzer, Tokenizer, Filterが何かわかったところで、上の設定のjsonを見ると、 settingsのところに、tokenizer, filter が設定されていて、それをanalyzerに設定している。 mappingsのところで、各フィールドにanalyzerを指定している、というのが解ると思います。

コマンド実行後にmappingを確認

curl -XGET 'localhost:9200/material_test/_mapping?pretty'
---
{
  "material_test" : {
    "mappings" : {
      "material" : {
        "properties" : {
          "description" : {
            "type" : "string",
            "analyzer" : "kuromoji_analyzer"
          },
          "name" : {
            "type" : "string",
            "analyzer" : "kuromoji_analyzer"
          }
        }
      }
    }
  }
}

文章の解析がいい感じになりました。

f:id:sissoko:20161122091408p:plain

以上が基本的な使い方でした。

弊社サービス「CREAS」ではどのように使っているか

creators.uuum.jp

CREASではクリエイター向けに様々な動画作成向けの素材を提供しています。

フレームワークはSymfonyを使っていて、

SymfonyでElasticsearchを簡単に扱えるFOSElasticaBundleの紹介 - UUUM攻殻機動隊

同ブログのnazo様の記事にSymfonyからElasticsearchをどのように使っているかが解りやすくまとめられています。

app/config/elastica.yml

に 上で話していた、analyzerなどの設定が書かれているので、見てみましょう。

settings部分抜粋

settings:
    index:
        analysis:
            tokenizer:
                kuromoji:
                    type: kuromoji_tokenizer
            analyzer:
                kuromoji_analizer:
                    type: custom
                    tokenizer: kuromoji
                    filter:
                        - kuromoji_baseform
                        - lowercase

kuromoji_analyzerが設定されています。

materialのmapping部分抜粋

material:
mappings:
id:
    type: integer
    index: not_analyzed
download_count:
    type: integer
    index: not_analyzed
type:
    type: string
    index: not_analyzed
material_categories:
    type: nested
    properties:
        id:
            type: integer
            index: not_analyzed
material_tags:
    type: nested
    properties:
        id:
            type: integer
            index: not_analyzed
        name:
            type: string
            index: not_analyzed
name:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
description:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
artist_name:
    type: string
    index: analyzed
    analyzer: kuromoji_analizer
artist_name_not_analyzed:
    type: string
    index: not_analyzed
    property_path: artist_name
artist_katakana:
    type: string
    index: not_analyzed
is_downloadable:
    type: boolean
    index: not_analyzed

name, descriptionなどはkuromoji_analyzerが設定されています。

id_downloadableなどのフラグは解析してもしょうがないので、 index: not_analzyed の指定がされています。 これにより、単純なフラグのon/offでの検索に対応することができます。

クエリ発行箇所

音源素材のtypeと、検索ワードから関連するデータを取得するメソッドの内容を一部抜粋

/**
     * キーワード検索結果でページネーション
     *
     * @param string $type MaterialType
     * @param string $word 検索キーワード
     * @return FOS\ElasticaBundle\Paginator\TransformedPaginatorAdapter
     */
    public function findPaginatedByKeywords($type, $word)
    {

...

        [
            'match' => [
   'is_downloadable' => true,
            ]
        ],
        [
            'query_string' => [
   'fields' => ['name', 'description', 'artist_name'],
   'query' => '"' . $word . '"',
            ],
        ],
        [
            'match' => [
   'type' => $type,
            ]
        ],

...

        return $this->elastica->createPaginatorAdapter('Material', $query);
    }

ダウンロードが可能(is_downloadable)で、対象の音源素材の種別から、 name, description, artist_name と検索ワードの関連があるデータを取得しているということが、わかると思います。

まとめ

kuromojiのfilterやtokenizerがよくできていたり、各種プラグインがよくできているので、触っているだけで結構面白くて、全文検索に対する理解も深まるのでオススメです!

参考にさせていただいたサイト