検索エンジン? として、Solr か Elasticsearch かで 迷ったあげく、
Elasticsearch を MacOS el capitan にインストールして、 java クライアントで検索できるように実装するまでの実施したことを記載します。


インストールする目的

Solr とどちらを使うか迷いましたが、
Postgress に登録してあるデータ(Fes情報)を logstash で Elasticsearch に登録し、
Kuromoji で わかち書きのindex 作成。
その結果を画面から検索できることを目的にインストールしました。


参考サイト


前提

以下、OS、Java、Postgress の Version について記載します。

  • OS

    sw_vers
    ----------------------------
    ProductName:    Mac OS X
    ProductVersion: 10.11.6
    BuildVersion:   15G1217
    ----------------------------
    

  • Java

    java -version
    ----------------------------
    java version "1.8.0_45"
    Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
    Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
    ----------------------------
    

  • Postgress

    psql --version
    ----------------------------
    psql (PostgreSQL) 9.5.2
    ----------------------------
    


実施したこと

実施したことは以下になります。
version は全て 5.2.2 がインストールされました。


Elasticsearchのインストール

brew install elasticsearch

brew install elasticsearch
-------------------------------
🍺  /usr/local/Cellar/elasticsearch/5.2.2: 98 files, 35.8MB, built in 6 minutes 58 seconds
-------------------------------

PATH 設定

何度のcommand を実行するので、elasticsearch の command をPATHに設定します。

vi ~/.bash_profile
export ELASTICPATH=/usr/local/opt/elasticsearch/libexec/bin/
export PATH=$PATH:$ELASTICPATH

plugin コマンドについて

command の名称は elasticsearch-plugin
接頭部にelasticsearch付与されています。
command の名称が 5.0.0 から変わったようです。

elasticsearch
elasticsearch-plugin
elasticsearch-systemd-pre-exec
elasticsearch-translog
elasticsearch.in.sh

[補足]自動起動設定

elasticsearch の 起動、停止を繰り返していたので、 実施していませんが、以下コマンドで自動起動設定、自動起動設定後の、起動、停止が可能です。

# 自動起動設定
ln -sfv /usr/local/Cellar/elasticsearch/5.2.2/*.plist ~/Library/LaunchAgents
# 起動
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.elasticsearch.plist
# 停止
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.elasticsearch.plist


Kuromojiプラグインのインストール

install

elasticsearch-plugin install analysis-kuromoji
---------------------------
-> Downloading analysis-kuromoji from elastic
[=================================================] 100%   
-> Installed analysis-kuromoji
---------------------------

elasticsearch 再起動

Ctrl + C で elasticsearch を停止 再度コマンド実行で起動して、pluginを認識させます。1
[1] 再起動しなくても認識されるかもしれません。

elasticsearch

plugin が認識されていることを確認

  • curl

    curl 'localhost:9200/_cat/plugins?v'
    -------------------------------------
    name    component         version
    JlcSImk analysis-kuromoji 5.2.2
    -------------------------------------
    

  • elasticsearch-plugin

    elasticsearch-plugin list
    -------------------------------------
    analysis-kuromoji
    -------------------------------------
    

kuromoji を使用するindex を作成。

5.0.0からelasticsearch.yml index の 設定ができなくなりました。
kuromoji_tokenizer をdefault の analyzer として使用するindex を作成します。
index 名 festivalこの後、logstash を使ってデータを流し込む index 名と一致させています。

curl -XPUT 'http://localhost:9200/festival' -d '
{ 
  "index":{
    "analysis":{
      "tokenizer" : {
        "kuromoji" : {
          "type":"kuromoji_tokenizer",
          "mode":"search" 
        }
      },
      "analyzer" : {
        "default" : {
          "type" : "custom",
          "tokenizer" : "kuromoji_tokenizer"
        }
      }
    }
  }
}'

default の Index 設定が効いているかを確認する

curl -XGET "http://localhost:9200/festival/_analyze?pretty" -d "ジャズストリート"
---------------------------------------------------
{
  "tokens" : [
    {
      "token" : "ジャズ",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "ストリート",
      "start_offset" : 3,
      "end_offset" : 8,
      "type" : "word",
      "position" : 1
    }
  ]
}
---------------------------------------------------

補足.設定ファイル編集

5.2.2 は、elasticsearch.ymldefault tokenizer設定はできなくなっています。
elasticsearch.yml default の analyzner を設定すると、
以下のエラーメッセージが出力されます。

  • yml ファイル編集

    cd /usr/local/etc/elasticsearch
    vi elasticsearch.yml
    ---------------------------------------
    ##########################################
    # set kuromoji as default Tokenizer
    ##########################################
    index.analysis.analyzer.default.type: custom
    index.analysis.analyzer.default.tokenizer: kuromoji_tokenizer
    ---------------------------------------
    

  • エラーメッセージ

    *************************************************************************************
    Found index level settings on node level configuration.
    
    Since elasticsearch 5.x index level settings can NOT be set on the nodes 
    configuration like the elasticsearch.yaml, in system properties or command line 
    arguments.In order to upgrade all indices the settings must be updated via the 
    /${index}/_settings API. Unless all settings are dynamic all indices must be closed 
    in order to apply the upgradeIndices created in the future should use index templates 
    to set default values. 
    
    Please ensure all required values are updated on all indices by executing: 
    
    curl -XPUT 'http://localhost:9200/_all/_settings?preserve_existing=true' -d '{
      "index.analysis.analyzer.default.tokenizer" : "kuromoji_tokenizer",
      "index.analysis.analyzer.default.type" : "custom"
    }'
    *************************************************************************************
    


Logstashのインストール

brew install logstash

brew install logstash
----------------------------
==> Caveats
Please read the getting started guide located at:
  https://www.elastic.co/guide/en/logstash/current/getting-started-with-logstash.html
==> Summary
🍺  /usr/local/Cellar/logstash/5.2.2: 10,588 files, 167.1MB, built in 21 minutes 51 seconds
----------------------------

PATH の設定

vi ~/.bash_profile
export LOGSTASHPATH=/usr/local/Cellar/logstash/5.2.2/bin/
export PATH=$PATH:$LOGSTASHPATH

logstash-input-jdbc のインストール

logstash-plugin install logstash-input-jdbc
---------------------
Validating logstash-input-jdbc
Installing logstash-input-jdbc
Installation successful
---------------------

logstash.conf の作成

以下のような、logstash.conf を作成しました。

input {
  jdbc {
    jdbc_driver_library => "{YOUR_POSTGRESS_JDBC_PATH}"
    jdbc_driver_class => "org.postgresql.Driver"
    jdbc_connection_string => "jdbc:postgresql://localhost:5432/{YOUR_DATABASE_NAME}"
    jdbc_user => "{YOUR_JDBC_USER}"
    jdbc_password => "{YOUR_JDBC_PASSWORD}"
    schedule => "* * * * *"
    statement => "SELECT id,name,site_url,description,create_date,update_date,held_year_id,end_date,start_date,encode(thumbalizr,'base64') as thumbalizr from festival" 
  }
}
output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "festival"
    doc_as_upsert => true
    document_id => "%{id}"
    action => "update"
  }
  stdout { codec => rubydebug }
}

logstash.conf の記載内容についての説明

  • jdbc_driver_library jdbc_driver_library は、jdbc_driver の配置先のパスを記載します。相対パスでも記載できそうですが、
    どこからの相対パスかが、わからなかったので、絶対パス記載で動作させました。

  • statement
    encode(thumbalizr,'base64') blog を base64エンコードしてElasticsearch に 流し込んでいます。
    Binary datatype | Elasticsearch Reference [5.2] | Elastic
    見る限り、Blob 値直接は設定できないと判断しました。

  • doc_as_upsert, document_id, action
    最初、指定なしでデータを登録したところ、重複データが大量にできてしまったので、doc_as_upsert document_id action指定で、
    DB側のキー値を明示して、upsert で登録するようにしました。2
    [2] action指定は不要かもしれません。

設定ファイルを読み込み、logstash 実行

logstash -f scripts/logstash.conf


Javaクライアントのインストールと、実装

上記の設定で、データの流し込みまで実施できたので、
アプリケーションの検索で使用するクライアントを実装します。
調べた限りだと、REST client や、 Transport client があるようです。
今回は、Transport client で実装しました。

pom.xml に dependency を追加

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.2.2</version>
</dependency>

設定情報を確認する

Transport client では、cluster.namenode.name設定する必要があります。3
[3] addTransportAddress ipアドレス設定すれば動くかと思いきや、自環境では動きませんでした。
以下 curl コマンドで、cluster名 と、 node名 が取得できます。

  • cluster名を取得

    curl 'localhost:9200/_cat/health?v'
    ---------------------------------------------------------
    epoch      timestamp cluster                  status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
    1490023819 00:30:19  elasticsearch_clustername yellow          1         1      5   5    0    0        5             0                  -                 50.0%
    ---------------------------------------------------------
    

  • node名を取得

    curl 'localhost:9200/_cat/nodes?v'
    ----------------------------------------------------------
    ip       heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
    10.0.1.4           50         100  15    2.47                  mdi       *      JlcSImk
    ----------------------------------------------------------
    

クライアントの実装

以下のようにメソッドは実装しました。keyword を引数に、match検索を行います。

    /**
     * get
     *
     * @param keyword
     * @return
     */
    public List<Festival> get(String keyword) {
        TransportClient client = null;
        try {
            client = new PreBuiltTransportClient(Settings.builder()
                    .put("cluster.name", "elasticsearch_clustername").put("client.transport.sniff", false)
                    .put("node.name", "JlcSImk")
                    .put("client.transport.ping_timeout", 20, TimeUnit.SECONDS).build())
                    .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("10.0.1.4"), 9300));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        Map<String, Object> template_params = new HashMap<>();
        template_params.put("keyword", keyword);
        SearchResponse sr = new SearchTemplateRequestBuilder(client)
                .setScript("{\n" +
                        "        \"query\" : {\n" +
                        "            \"match\" : {\n" +
                        "                \"name\" : \"{{keyword}}\"\n" +
                        "            }\n" +
                        "        }\n" +
                        "}")
                .setScriptType(ScriptType.INLINE)
                .setScriptParams(template_params)
                .setRequest(new SearchRequest())
                .get()
                .getResponse();
        SearchHit[] results = sr.getHits().getHits();
        List<Festival> festivals = new ArrayList<>();
        for (SearchHit hit : results) {

            String sourceAsString = hit.getSourceAsString();
            if (sourceAsString != null) {
                ObjectMapper mapper = new ObjectMapper();
                Festival fes = null;
                try {
                    fes = mapper.readValue(sourceAsString, Festival.class);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                festivals.add(fes);
            }
        }
        client.close();
        return festivals;
    }

クライアントの実装の説明

  • PreBuiltTransportClient
    PreBuiltTransportClient引数にBuilderを設定します。ポート番号はデフォルトだと9300 で、HTTPとは別でポートが空いています。

  • SearchTemplateRequestBuilder
    String 文字列直接でクエリの生成もできますが、こちら参考に
    SearchTemplateRequestBuilder を使用する形で実装しました。

  • 戻り値の変換
    Jackson の ObjectMapper使って戻りのJSON文字列をJAVAクラスに変換しました。
    curl コマンドで Elasticsearch からは、以下の戻りが返ってきてましたので、

    curl -XGET localhost:9200/festival/_search?pretty -d '{"query":{"match":{"name":"高槻"}}}}' 
    -------------------------------------------------
    {
      "took" : 124,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
      },
      "hits" : {
        "total" : 1,
        "max_score" : 4.3933816,
        "hits" : [
          {
            "_index" : "festival",
            "_type" : "logs",
            "_id" : "270",
            "_score" : 4.3933816,
            "_source" : {
              "end_date" : null,
              "@timestamp" : "2017-03-20T15:45:00.276Z",
              "site_url" : "http://www.0726.info/",
              "held_year_id" : 8,
              "name" : "高槻ジャズストリート",
              "@version" : "1",
              "description" : "",
              "thumbalizr" : null,
              "id" : 270,
              "create_date" : "2016-12-11T14:38:59.740Z",
              "update_date" : "2016-12-11T14:38:59.740Z",
              "start_date" : null
            }
          }
        ]
      }
    }   
    -------------------------------------------------
    
    _source部分をINPUT に jsonschema2pojoJAVAのクラスを生成し、JSON文字列から
    マッピングしています。

TODO

上記の実装で、JAVAから Elasticsearch の 検索結果の取得ができるようになりました。
ローカルで実行している感じだと、接続オブジェクトの時間がかかっているようで、
結構もっさりしています。ALL IN ONE で DBサーバーも、APサーバーも一緒くただと、
RDB のほうが早かったりしそうです。
接続オブジェクトCache とか、 NodeClient を使う等このあたりは改善できるのかもしれないので、
後日実施してみようかと思います。

以上です。

コメント

カテゴリー