まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:elasticsearch

Bonsai Elasticsearch はその名前の通り、高速多機能な検索エンジンである Elasticsearch による検索機能をダッシュボード機能も含めてマネージドサービスとして提供するプロバイダーです。日本語の情報が少ないせいか、技術記事含めて情報自体を見ることがあまりありませんでした。偉そうに言ってますが、自分も以下の件で興味を持つまでは存じ上げていませんでした:
2021083002


Bonsai Elasticsearch は Salesforce から提供されている PaaS 環境である herokuサードパーティサービスの1つでもあります。heroku 自体も無料で使える利用枠が提供されていて、また Bonsai Elasticsearch にも無料枠があるため、heroku を経由してサービスを申し込むと「検索エンジンの使えるアプリケーションを無料で開発・運用できる環境」を手に入れることができます。世に多くのクラウド環境はありますが、Elasticsearch が無料で使えるアプリケーション・サーバー環境、という時点でかなり珍しいといえます:
2021083003


一方で、現実的に日本人を対象とするアプリケーションを考えると、検索機能は日本語検索ができなければあまり実用的とはいえません。英語のように単語と単語の間に明確なスペースが入って区切られるわけではない日本語は、テキスト内の単語と単語の区切りを見つける時点でかなりハードルが高く、そこから更に検索インデックスを作る必要があるためです。ここまでの機能がサポートされていないと検索エンジンとしては使いにくいのでした。 例えば自分で自分の(手元の)PC やサーバーに Elasticsearch をインストールした場合であれば、自分で更に日本語形態素解析機能を追加でインストールすればいいのですが、クラウドのマネージドサービスとして提供されている場合、そのような権限をもらえないことも多いため、サービスとして提供されている機能の中に多言語対応(日本語対応)が含まれていないといけない、という高めのハードルが設定されているのでした。

そんな中で見つけたこの Bonsai Elasticsearch 。ネットの日本語情報が少ないということは、日本語が使えないということかな・・・ と勝手に想像していました。 が、Bonsai のドキュメントを調べてみると Bonsai Elasticsearch に始めから導入されているプラグイン一覧の中に日本語形態素解析である "Kuromoji Analyzer" の文字を見つけました。あれ?これはもしかして期待できるやつ?:
https://docs.bonsai.io/article/135-plugins-on-bonsai

2021083001


というわけで、実際に Bonsai Elasticsearch で日本語検索ができるかどうかを調べてみました。結論としてはどうやら使えそう(!!!)だと思ってます。以下はその際の記録です。


【調査に使ったシナリオ】
最初に迷ったのは「何を調べれば日本語検索ができるといえそうか」でした。日本語データを入れて、日本語で検索して、日本語のそれっぽいデータが返ってきたらいいのか?? というと、そうともいえないし・・・

で、今回は Elastic 社の日本語エンジニアリングブログから提供されていた『Elasticsearchで日本語のサジェストの機能を実装する』というエントリで紹介されていた方法が Bonsai Elasticsearch でできるかどうかを調べました。詳細はリンク先を確認していただきたいのですが、大まかな内容としては kuromoji を使う前提で日本語でのサジェスト機能を実装するためのインデックスおよびマッピングを作成し、日本語のデータを入れた上で検索して結果を見る、というものです。ここで紹介されているのは入力ミスまでを考慮した曖昧な検索を行うという内容で、この結果が期待通りになればそれはもう大丈夫でしょ、という判断です。

ちなみに同ページで紹介されている内容自体が日本語サジェストを実現するための考え方なども紹介されていて非常に有用でした。その上で、ここで紹介されていることと同じ内容が Bonsai Elasticsearch に対して行っても同じ結果になるか(Bonsai Elasticsearch で日本語形態素解析が使えれば同じ結果になるはず)、を試してみました:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch


【Bonsai Elasticsearch の準備】
まずは Bonsai Elasticsearch のインスタンスを準備します。自分の場合は heroku 経由でインスタンスを作成したので、その場合の手順を紹介します。

まず heroku で無料アカウントを作成し、クレジットカードを登録しておきます(無料版の Bonsai Elasticsearch を使いますが、そのためには heroku アカウントにクレジットカードの登録が必要です)。改めてブラウザで heroku にログインし、(必要であれば)アプリケーションを1つ作成した上でそのアプリケーションにアドオンとして紐付ける形で Bonsai Elasticsearch を追加します。アプリケーションを選択して、"Overview" タブから "Configure Add-ons" を選択します:
2021083004


アドオンを選択する画面で検索ボックスに "Bonsai" と入力すると "Bonsai Elasticsearch" が見つかるのでこれを選択します:
2021083005


こんな感じの確認画面が表示されたら、無料プランの "Sandbox -  Free" が選択されていることを確認して(これ以外は有料です) "Submit Order Form" ボタンをクリックします:
2021083006


正しく処理されると、アプリケーションのアドオン一覧に "Bonsai Elasticsearch" が追加されます:
2021083007


追加された Bonsai Elasticsearch にアクセスするための接続 URL を確認します。同アプリケーションの "Settings" タブから "Reveal Config Vars" ボタンをクリックします:
2021083008


すると、このアプリケーションの動作時に設定される環境変数の一覧が表示されます。さきほど、Bonsai Elasticsearch を追加した際のオペレーションで "BONSAI_URL" という環境変数が設定されているはずです:
2021083009


ここでコピーした BONSAI_URL の値は、このようなフォーマットのテキストになっているはずです:
https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443

この値をこの後のインデックス作成やクエリー実行時に使うことになります。何度も使うことになるのでコピペできるよう、どこかに退避しておきましょう。

ここまで完了すれば Bonsai Elasticsearch の準備は環境です。


【Bonsai Elasticsearch に日本語インデックスと日本語データを作成して検索】
ここまでの準備ができれば Elastic 社の日本語エンジニアリングブログで紹介されていた、この内容を実際に試すことができるようになります:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch

ただ具体的なコマンド入力を考慮すると、このままだと少しわかりにくいため、この内容がもう少し試しやすくなるようなファイルやコマンド集(なんなら実行結果も含まれてますw)を作って公開しました。単に挙動や結果だけを確認したい人はこちらをダウンロードして使ってください:
https://github.com/dotnsf/bonsai_elasticsearch


以下に curl を使った具体的な手順とともに紹介します。日本語エンジニアリングブログによると、最初に kuromoji を使った日本語インデックスを作成する必要があります。上述でダウンロードしたファイルの中に含まれている my_suggest.json が日本語インデックスとマッピングを構成するファイルなので、以下の curl コマンドを実行してこれを Bonsai Elasticsearch に PUT します:
$ curl -XPUT https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest -d @my_suggest.json -H 'Content-Type: application/json'

https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443 部分は先述の BONSAI_URL の値に置き換えて指定してください。以下同様)

次に検索対象となる日本語データをまとめてバルクアップロードします。データファイルは japanese.json です(厳密には JSON フォーマットではなく、複数の JSON が繋がっているフォーマットです。そのため後述のコマンドで特殊な Content-Type を指定する必要があります)。このファイルを以下の curl コマンドで Bonsai Elasticsearch に POST します:
$ curl -XPOST https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_bulk --data-binary @japanese.json -H 'Content-Type: application/x-ndjson'

(Content-Type の指定値が 'application/json' ではなく、'application/x-ndjson' となっている点に注意してください)

これで Bonsai Elasticsearch に日本語インデックスと日本語データが格納されました。これらが正しく日本語で検索できるかどうか(このブログの本来の目的でいえば、5種類の曖昧な日本語検索をしても同じ結果になるかどうか)を確認してみます。5種類の検索クエリーを5つのファイル(query1.json, query2.json, ..., query5.json)で用意したので、5回に分けてそれぞれを実行し、その結果を result1.json, result2.json, ..., result5.json に格納します:
$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query1.json -H 'Content-Type: application/json' > result1.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query2.json -H 'Content-Type: application/json' > result2.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query3.json -H 'Content-Type: application/json' > result3.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query4.json -H 'Content-Type: application/json' > result4.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query5.json -H 'Content-Type: application/json' > result5.json

ちなみにこれらの検索の内容ですが、「日本」という文字を多く含む日本語データがある程度格納されている状況下で、query1.json では「日本」を、query2.json では「にほn」を、query3.json では「にhん」を、query4.json では「にっほん」を、そして query5.json では「日本ん」を検索します。このように入力ミスまで考慮した曖昧な検索をしても同じ検索結果になるようなインデックスとマッピングを作る、という内容でした。実際に result1.json から result5.json まで見ると、検索結果はどれも一致しているはずです。


・・・というわけで、無料の Bonsai Elasticsearch (と導入済みの kuromoji)を使って日本語インデックスで日本語データを期待通りに検索できることがわかりました。こりゃ、いいモン見つけたかも!



IBM Cloud から提供されている 30 日間無料 Kubernetes サービスIBM Kubernetes Service 、以下 "IKS")環境を使って利用することのできるコンテナイメージを1日に1個ずつ 30 日間連続で紹介していきます。

環境のセットアップや制約事項については Day0 のこちらの記事を参照してください。

Day 6 からはデータベース系コンテナとその GUI ツールを中心に紹介してます。Day 14 では超強力な検索エンジンである ElasticSearch イメージをデプロイする例を紹介します。
es



【イメージの概要】
検索エンジンです。データベースに格納したデータは SQL 文で柔軟に検索することも可能ですが、検索のパフォーマンスを上げる目的で専用検索エンジンを併用することは珍しくありません。また例えば「白い」という検索で「白く」という文字を含むデータを検索するなどといった曖昧な検索や、同義語を定義した検索など、より実用的な検索機能を実現できます。



【イメージのデプロイ】
まずはこちらのファイルを自分の PC にダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/yamls_for_iks/main/elasticsearch.yaml


今回の ElasticSearch もシングルノード環境で利用する場合は特にパラメータ指定不要で、そのままデプロイすることができます。以下のコマンドを実行する前に Day 0 の内容を参照して ibmcloud CLI ツールで IBM Cloud にログインし、クラスタに接続するまでを済ませておいてください。

そして以下のコマンドを実行します:
$ kubectl apply -f elasticsearch.yaml

以下のコマンドで ElasticSearch 関連の Deployment, Service, Pod, Replicaset が1つずつ生成されたことと、サービスが 30200, 30300 番ポートで公開されていることを確認します:
$ kubectl get all

NAME                                 READY   STATUS    RESTARTS   AGE
pod/elasticsearch-7bbff9597c-wz22n   1/1     Running   0          15s

NAME                    TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                         AGE
service/elasticsearch   NodePort    172.21.61.7   <none>        9200:30200/TCP,9300:30300/TCP   17s
service/kubernetes      ClusterIP   172.21.0.1    <none>        443/TCP                         27d

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/elasticsearch   1/1     1            1           16s

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/elasticsearch-7bbff9597c   1         1         1       17s

この後に実際にサービスを利用するため、以下のコマンドでワーカーノードのパブリック IP アドレスを確認します(以下の例であれば 161.51.204.190):
$ ibmcloud ks worker ls --cluster=mycluster-free
OK
ID                                                       パブリック IP    プライベート IP   フレーバー   状態     状況    ゾーン   バージョン
kube-c3biujbf074rs3rl76t0-myclusterfr-default-000000df   169.51.204.190   10.144.185.144    free         normal   Ready   mil01    1.20.7_1543*

つまりこの時点で(上述の結果であれば)アプリケーションは http://169.51.204.190:30200/ で稼働している、ということになります。早速実行してみます。ウェブブラウザか curl コマンドを使って、アプリケーションの URL(上述の方法で確認した URL)にアクセスしてみます:
2021071103



【YAML ファイルの解説】
YAML ファイルはこちらを使っています。シングルノードモードで動かすための設定です:
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
spec:
  selector:
    app: elasticsearch
  ports:
  - port: 9200
    name: port1
    protocol: TCP
    targetPort: 9200
    nodePort: 30200
  - port: 9300
    name: port2
    protocol: TCP
    targetPort: 9300
    nodePort: 30300
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: docker.elastic.co/elasticsearch/elasticsearch:7.13.2
        env:
        - name: discovery.type
          value: "single-node"
        ports:
        - containerPort: 9200
        - containerPort: 9300

Deployment 1つと、Service 1つのごくごくシンプルな YAML ファイルですが、一応解説を加えておきます。アプリケーションそのものは 9200 番ポートおよび 9300 番ポートで動作するように作られているため、NodePort 30200 番、30300 番をそれぞれ指定して、外部からは 30200 番ポートでアクセスできるようにしています(NodePort として指定可能な番号の範囲は 30000 ~ 32767 です、指定しない場合は空いている番号がランダムに割り振られます)。また ReplicaSet は1つだけで作りました(データベースなので、別途クラスタ構成の準備をしない限りはこの数値だけを増やしてもあまり意味ないと思います)。


デプロイしたコンテナイメージを削除する場合はデプロイ時に使った YAML ファイルを再度使って、以下のコマンドを実行します。不要であれば削除しておきましょう(ちなみにこの ElasticSearch コンテナは明日の Day 15 でも使う予定なので、削除するのはそのあとの方がいいかもしれません):
$ kubectl delete -f elasticsearch.yaml


【紹介したイメージ】
https://docker.elastic.co/elasticsearch/elasticsearch


【紹介記録】
Dayカテゴリーデプロイ内容
0準備準備作業
1ウェブサーバーhostname
2Apache HTTP
3Nginx
4Tomcat
5Websphere Liberty
6データベースMySQL
7phpMyAdmin
8PostgreSQL
9pgAdmin4
10MongoDB
11Mongo-Express
12Redis
13RedisCommander
14ElasticSearch
15Kibana
16CouchDB
17CouchBase
18HATOYA
19プログラミングNode-RED
20Scratch
21Eclipse Orion
22Swagger Editor
23R Studio
24Jenkins
25アプリケーションFX
262048
27DOS Box
28VNC Server(Lubuntu)
29Drupal
30WordPress

ラズパイ(ラズベリーパイ)上で検索エンジンである ElasticSearch が動くことがわかったので、自分も試してみました:
2017070601


まず、ElasticSearch そのものにラズパイネイティブ版が存在しているわけではありません。 ElasticSearch は Java アプリケーションなので実行には Java が必要であり、Java が有効な環境であれば理論上は動きます。というわけで最初にラズパイに JDK 8 を導入します(導入済みであれば、ここは読み飛ばしても可)。
$ sudo apt-get install oracle-java8-jdk

$ java -version
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

Java が使えるようになったので改めて ElasticSearch を導入します。ダウンロードページで確認すると、この記事を書いている 2017/Jul/06 時点での最新バージョンは 5.4.3 でした:
2017070602



これをラズパイ上にダウンロードして展開します:
$ cd ~
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.3.zip
$ unzip elasticsearch-5.4.3.zip
$ rm elasticsearch-5.4.3.zip
$ mv elasticsearch-5.4.3 elasticsearch
$ cd elasticsearch

さて、他のプラットフォームだとこのまま実行してもいいのですが、ラズパイの場合はシステムスペックの問題でデフォルトの設定のままだとメモリ不足になりやすいという問題があります。そこで実行前にメモリの設定を変更しておきます。22 行目と 23 行目(か、そのあたり)で、"-Xms2g" と "-Xmx2g" という指定がされており、これによって ElasticSearch に 2GB のメモリを使うよう指定されています。ここを以下の赤字のように書き換えて、メモリ使用量を 256MB にするよう変更します:
$ vi config/jvm.options

  :
  :
-Xms256m   ← -Xms2g から変更
-Xmx256m   ← -Xmx2g から変更
  :
  :

これでメモリの問題は解決した(はず)なので、改めて ElasticSearch を起動します:
$ ./bin/elasticsearch
[2017-07-06T10:41:17,203][WARN ][o.e.b.Natives ] unable to load JNA native support library, native methods will be disabled. : : [2017-07-06T10:41:39,511][INFO ][o.e.h.n.Netty4HttpServerTransport] [lonXjYa] publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200} [2017-07-06T10:41:39,537][INFO ][o.e.n.Node ] [lonXjYa] started [2017-07-06T10:41:39,579][INFO ][o.e.g.GatewayService ] [lonXjYa] recovered [0] indices into cluster_state

いくつかの警告メッセージが表示されますが、ラズパイ上で ElasticSearch 5.4.3 が起動しています。確認のため、同じマシンから curl でアクセスしてみましょう:
$ curl http://localhost:9200/
{
  "name" : "lonXjYa",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "4hqvLhXQT0uqTaKOI02idg",
  "version" : {
    "number" : "5.4.3",
    "build_hash" : "eed30a8",
    "build_date" : "2017-06-22T00:34:03.743Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}

↑こんな感じのメッセージが表示されれば成功です。なお、ElasticSearch を終了するには実行中の端末で Ctrl+C を入力します:
    :
    :
[2017-07-06T10:41:39,537][INFO ][o.e.n.Node               ] [lonXjYa] started
[2017-07-06T10:41:39,579][INFO ][o.e.g.GatewayService     ] [lonXjYa] recovered [0] indices into cluster_state

^C[2017-07-06T10:52:09,484][INFO ][o.e.n.Node               ] [lonXjYa] stopping ...
[2017-07-06T10:52:09,579][INFO ][o.e.n.Node               ] [lonXjYa] stopped
[2017-07-06T10:52:09,581][INFO ][o.e.n.Node               ] [lonXjYa] closing ...
[2017-07-06T10:52:09,668][INFO ][o.e.n.Node               ] [lonXjYa] closed

$ 
  (↑Ctrl+C(赤字部分)を入力して終了する様子)


実はいま、個人的にはラズパイを開発環境として使う機会がそれなりにあります(ラズパイにリモートログインして vi でガシガシ、という感じ)。そのローカルシステム内に ElasticSearch 環境まで構築できる時代になったとは・・・今以上に開発がはかどりますね。


ElasticSearch で英語以外のテキストを使った検索エンジンを実装しようとすると、多くの場合 ICU(International Components for Unicode) というプラグインを使うことになります。特に日本語を扱う場合は kumoroji プラグインとセットで使うことが多いようですが、日本語文字列を正規化してフィルタリングをかける場合のデファクトスタンダードになっている組み合わせだと思います。

この ICU プラグインを ElasticSearch に導入する方法は ICU プラグインのホームページに記載されています:
https://github.com/elastic/elasticsearch-analysis-icu


具体的な導入方法は上記ページ内に記述されているように、コマンドラインから
# bin/plugin -install elasticsearch/elasticsearch-analysis-icu/(ICU のバージョン番号)

と入力・・・ だと思っていました。ところがこの方法は ElasticSearch のバージョンが 1.x までの頃の話でした:
2016111801


現在、多くの人が使っている ElasticSearch のバージョンは 2.x 以上だと思っています(2016/Nov/18 時点の最新バージョンは 5.0)。では ElasticSearch 2.x で ICU を導入するにはどのようにすればいいのでしょうか?

答はこれでした。かなりシンプルになりました:
# bin/plugin -install analysis-icu

これ、公式のどこかに書いてないのかな・・・

スケーラブルな検索エンジンである ElasticSearch は Ver.2 以降の仕様でデフォルト設定では localhost からのアクセスのみが許可されるようになりました。要するにデフォルト設定のままでは外部からの検索リクエストを受け付けない、ということになります。

このままでも問題ない、という人もいると思いますが、検索サービスとして利用するケースを想定すると、このままでは外部に検索 API 機能を提供できないことになってしまいます。外部からのリクエストを受け付けるには elasticsearch.yml(CentOS などであれば /etc/elasticsearch/elasticsearch.yml)を編集して、以下の1行を追加します:
network.host: 0.0.0.0

この状態で ElasticSearch を再起動すると外部アクセスが可能になります。

ただし、この指定だと本当にどこからでも使えてしまいます。つまり外部から DELETE リクエストでインデックスごと消す、なんてことも出来てしまいます。それはそれで危険かもしれません。実際の運用においては特定のアドレス(範囲)からのアクセスだけ受け付けるようにする、などの工夫をすべきだと思います。

このページのトップヘ