Node-REDでのElasticsearch活用法

# はじめに
Node-REDでよくElasticsearchへデータ投入するので、ノウハウを書きたいです。
追加ノードでも色々ありますが、Elasticsearchはバージョンの更新が早く、APIも変わる事があります。
そのため、標準のノードを組み合わせての利用が無難かと思ってます。

# ソフトウェア環境
- Node-RED v1.0.3
- Elasticsearch 7.3.2

## サブフロー化
まずは、Elasticsearchへ通信するhttpノードをサブフロー化します。これにより、BASIC認証などの情報をノードを設置する際に毎回設定しなくて済みます。あと、常にヘッダに"Content-Type: 'application/json'"を設定するようにしましょう。これで少し、Elasticsearchへの接続設定の手間が省けます。
URLやメソッドはリクエストにより変化するため、サブフロー内では設定しないようにしましょう。

![スクリーンショット 2019-12-07 19.29.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/b039380e-dcc5-634d-63eb-e72b71440561.png)

書き出したコードは以下です。

```json
[{"id":"3f7774f5.a6a20c","type":"subflow","name":"Elasticsearch","info":"","category":"","in":[{"x":50,"y":30,"wires":[{"id":"c5bb15c.4c10de8"}]}],"out":[{"x":580,"y":30,"wires":[{"id":"19d2a3b.d048a5c","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"eeda88c1.f8b278","type":"http request","z":"3f7774f5.a6a20c","name":"Elasticsearch","method":"use","ret":"txt","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"basic","x":340,"y":30,"wires":[["19d2a3b.d048a5c"]]},{"id":"c5bb15c.4c10de8","type":"change","z":"3f7774f5.a6a20c","name":"msg.headers","rules":[{"t":"delete","p":"headers","pt":"msg"},{"t":"set","p":"headers","pt":"msg","to":"{\"Content-Type\":\"application/json\",\"Connection\":\"close\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":170,"y":30,"wires":[["eeda88c1.f8b278"]]},{"id":"19d2a3b.d048a5c","type":"json","z":"3f7774f5.a6a20c","name":"","property":"payload","action":"","pretty":false,"x":490,"y":30,"wires":[[]]}]
```

## 1. データ投入
Linkingデバイスという、環境センサーからのビーコンを受信し、Elasticsearchへデータ投入する例です。

![スクリーンショット 2019-12-07 19.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/5ee51f15-ec37-59d6-8a9d-c1b8a38ee9c6.png)

linking-scannerノードからは以下のようなデータがペイロードとして渡されます。

![スクリーンショット 2019-12-07 19.42.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/cff55f56-8f3e-df3a-8c8e-02b0c349037e.png)

このデータをElasticsearchへ投入するために、Changeノードで msg.method と msg.url を設定します。
msg.urlは前のノードの値を使って、動的なURLを生成します。
プログラムを組める方はfunctionノードに頼りがちですが、実は使わずに生成できます。

![スクリーンショット 2019-12-07 19.46.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/70a17c1c-d362-4ccc-0dd5-a382f251b665.png)

ついでに、JSONata式を使えば、現時刻もChangeノードで生成できます。

![スクリーンショット 2019-12-07 19.51.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/4b19aa82-4247-051b-17ed-1bc16040e58d.png)

Elasticsearchへ投入するJSONデータをテンプレートノードで生成します。
Mustacheテンプレートを使えば、msgオブジェクト内のデータが使えます。

![スクリーンショット 2019-12-07 20.04.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/9d017db6-52b6-bdb4-a297-8db0e210d149.png)

このフローを書き出したものがこちらです。(Elasticsearchサブフローも含まれます)

```
[{"id":"3f7774f5.a6a20c","type":"subflow","name":"Elasticsearch","info":"","category":"","in":[{"x":50,"y":30,"wires":[{"id":"c5bb15c.4c10de8"}]}],"out":[{"x":580,"y":30,"wires":[{"id":"19d2a3b.d048a5c","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"eeda88c1.f8b278","type":"http request","z":"3f7774f5.a6a20c","name":"Elasticsearch","method":"use","ret":"txt","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"basic","x":340,"y":30,"wires":[["19d2a3b.d048a5c"]]},{"id":"c5bb15c.4c10de8","type":"change","z":"3f7774f5.a6a20c","name":"msg.headers","rules":[{"t":"delete","p":"headers","pt":"msg"},{"t":"set","p":"headers","pt":"msg","to":"{\"Content-Type\":\"application/json\",\"Connection\":\"close\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":170,"y":30,"wires":[["eeda88c1.f8b278"]]},{"id":"19d2a3b.d048a5c","type":"json","z":"3f7774f5.a6a20c","name":"","property":"payload","action":"","pretty":false,"x":490,"y":30,"wires":[[]]},{"id":"e6695b94.08cca8","type":"subflow:3f7774f5.a6a20c","z":"f4557e75.b0f3f","x":640,"y":140,"wires":[[]]},{"id":"7108220c.1b5d9c","type":"change","z":"f4557e75.b0f3f","name":"POST _doc","rules":[{"t":"set","p":"method","pt":"msg","to":"POST","tot":"str"},{"t":"set","p":"url","pt":"msg","to":"http://localhost:9200/sensor-","tot":"str"},{"t":"change","p":"url","pt":"msg","from":"$","fromt":"re","to":"payload.service","tot":"msg"},{"t":"change","p":"url","pt":"msg","from":"$","fromt":"re","to":"/_doc","tot":"str"},{"t":"set","p":"date","pt":"msg","to":"$now()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":140,"wires":[["69e18d2a.9e18d4"]]},{"id":"69e18d2a.9e18d4","type":"template","z":"f4557e75.b0f3f","name":"ES Query JSON","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{\n    \"date\": \"{{{date}}}\",\n    \"device\": \"{{{payload.device}}}\",\n    \"{{payload.service}}\": {{{payload.data}}}\n}","output":"str","x":460,"y":140,"wires":[["e6695b94.08cca8"]]},{"id":"d541687c.a302d8","type":"linking-scanner","z":"f4557e75.b0f3f","name":"","autostart":true,"duration":"","interval":"30","x":100,"y":140,"wires":[["7108220c.1b5d9c"]]}]
```

## 2. 時系列クエリ
最新の温度を検索する場合のフローです。シンプルに、ChangeノードとElasticsearchサブフローのみです。

![スクリーンショット 2019-12-07 20.26.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/cc252565-d9f5-c198-a9f4-8be98a11c9f2.png)

Changeノードは以下のように、msg.method と msg.url と、ms.payloadに検索クエリを設定します。

![スクリーンショット 2019-12-07 20.29.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/253c4bfc-eebd-935c-dc89-ef0191343980.png)

Elasticsearchの検索クエリは以下のようになってます。日付をDESCにして最初の１レコードのみを要求します。

![スクリーンショット 2019-12-07 20.32.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/09b8046c-2f1e-559c-e174-5518c931e913.png)

結果は以下のようになります。 msg.payload.hits.hits[0]._source にデータが返ってきます。

![スクリーンショット 2019-12-07 20.27.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/8240c7f3-7fda-1a5a-408b-d94b762863b7.png)

直近10分間の平均は以下のような検索クエリにしています。

![スクリーンショット 2019-12-07 20.44.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/9d6ee427-d124-5187-6163-ad4a1876ce80.png)

msg.payload.aggregations.temperature_avg.buckets[0].aggs.value に入ってますね。

![スクリーンショット 2019-12-07 20.45.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/7ac3dfad-ef21-f6d9-efb3-822fa20d8c5a.png)

## 3. 単語解析

Elasticsearchでは分かち書きのような事もできます。

![スクリーンショット 2019-12-07 21.08.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/e2970685-ca7d-c207-6523-8373920446f9.png)

日本語を単語分したい場合は、Elasticsearchのプラグインanalysis-kuromojiをインストールする必要があります。

![スクリーンショット 2019-12-07 21.10.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/c3e443ba-6f54-4cd3-7496-e4d8305b88c4.png)

結果は、msg.payload.tokens[]に入ってます。

![スクリーンショット 2019-12-07 21.12.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/87deb652-b2c3-d549-bd17-15bcde22e024.png)

単語が取れれば、単語についての分析などが実装できると思います。

settings , mappings もできますし、ファイルノードを組み合わせてユーザ辞書の更新もNode-REDでしています。

# おわりに
個人的に、Node-REDとElasticsearchは非常に相性良く組み合わせて使う事ができると考えています。
実際に仕事ではオリジナルのデータに分析データを埋め込み、Kibanaで可視化をしています。