なにこれ
MeiliSearchを使ってみるメモ。
現時点で知っているMeiliSearchの情報は以下の通り。
- Rust製
- 早そうだし、GCのstop the worldが無いの良い
- 日本語対応している
- しかも面倒な設定はしなくとも良いらしい
- シンプル
- Elasticsearchと比較してかなりシンプルらしい(触ってみた人談)
- Dockerイメージ配布してる
- ローカルで検証、開発しやすい
- SaaS提供はしていない
- 2021年11月現在ではクラウドホスト版のクローズドβを行っている状況
詳しくは公式ページでMeiliSearch
dockerで起動する
がっつり書いています。
これをそのまま行えばMeiliSearchコンテナが立ち上がるのですが、データ永続化を行いたいし、Docker Composeで立ち上げたいので以下のファイルを用意します。
version: "3.7"
services:
meilisearch:
container_name: meilisearch
image: getmeili/meilisearch:v0.24.0
volumes:
- example-meili-data:/data.ms
environment: []
ports:
- 7700:7700
volumes:
example-meili-data:
driver: local環境変数とかデータボリュームをどこに乗せるのかなどについてはyaginceさんの記事を参考にしました。 https://zenn.dev/yagince/articles/037746ab3ebfd1
そしてファイルができたらいつものようにdocker-compose upを行います。これでコンテナが立ち上がります。
http://localhost:7700にアクセスするとMini Dashboardという管理画面っぽいものが表示されます。
いくつかリンクをクリックするとなにを行えば良いかわかるように公式ページに飛んでくれます。
ここでドキュメントを見て気になったのですが、v1.0までは各バージョンでの互換性は無いようです。
https://docs.meilisearch.com/learn/getting_started/installation.html#updating-meilisearch
ただアップデートガイドは存在するので、これを見ながら対応することがあるかもしれませんね。
ざっくりとAPIを眺めてみる
APIドキュメントを見てみます。
https://docs.meilisearch.com/reference/api/
リクエスト形式
REST APIの構成になっているようです。
APIにリクエストした内容は非同期に処理される構成をとっているので、沢山リクエストを行っても受け付けてくれて実際の処理はキューに突っ込んでくれるとのことです。
また、リクエストにはJSON, NDJSON, CSV形式のいづれかで行えるようです。レスポンスは常にJSONです。
…NDJSONって何者でしょうか。
NDJSONは改行区切りJSONとのことで、newline delimited JSONを略しています。
配列の終了を待たずに1行づつ処理できるので順次処理をする場合に有用であるようです。
http://ndjson.org/ https://qiita.com/suin/items/246691382ea2a2b22031
APIを叩いてみる
叩いてみましょう。試してみないとなんとも言えないですしね。
index作成
indexを作成してみます。
indexはdocumentを最初に追加されたときか、index作成エンドポイントを叩いた時に作成されるとのことです。
ここではindex作成のエンドポイントを叩いてみます。
indexリソースについてのドキュメントはこちら。
https://docs.meilisearch.com/reference/api/indexes.html
index作成についてのドキュメントはこちら。
https://docs.meilisearch.com/reference/api/indexes.html#create-an-index
➜ ~ curl \
-X POST 'http://localhost:7700/indexes' \
-H 'Content-Type: application/json' \
--data-binary '{
"uid": "users",
"primaryKey": "id"
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 186 100 138 100 48 5307 1846 --:--:-- --:--:-- --:--:-- 7153
{
"uid": "users",
"name": "users",
"createdAt": "2021-11-28T09:18:54.587129500Z",
"updatedAt": "2021-11-28T09:18:54.588908300Z",
"primaryKey": "id"
}成功しました。
リクエストボディについては以下の通りです。
- uid
- インデックス名。全体で一意になるようにセットする必要がある。
- primaryKey
- ドキュメントを一意に識別する主キー。設定しない場合はMeiliSearchが推論するらしい。
諸々ドキュメントを漁ったのですが、ElasticsearchのMappingのような記述は見当たりませんでした。
なので単純にindexを作成すればドキュメントを登録する準備ができるのか。
良し悪しは使っていくうちに見えてくると思いますが、シンプルなのは良いですね。
document登録
いくつかdocumentを登録してみます。
documentリソースのドキュメントはこちら。 https://docs.meilisearch.com/reference/api/documents.html
登録のドキュメントはこちら。 https://docs.meilisearch.com/reference/api/documents.html#add-or-replace-documents
POSTは追加or置換とのことです。これはドキュメントを完全に上書きします。
PUTは追加or更新です。こっちは部分的にドキュメントを更新します。
普通のRESTですね。
早速JSONで思いつく型で元々登録してみましょう。
まずはPOSTから。
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/documents' \
-H 'Content-Type: application/json' \
--data-binary '[{
"id": 1,
"name": "鈴木 一郎",
"name_kana": "スズキ イチロウ",
"job_change_count": 2,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+1@example.com",
"test+2@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
},
{
"id": 200,
"label": "経営企画"
}
]
}]' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 445 100 14 100 431 1750 53875 --:--:-- --:--:-- --:--:-- 55625
{
"updateId": 0
}登録できました。Mini Dashboardを見てみるとこんな感じ。
次はPUT
➜ ~ curl \
-X PUT 'http://localhost:7700/indexes/users/documents' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 2,
"name": "骨川 スネ夫",
"name_kana": "ホネカワ スネヲ",
"job_change_count": 0,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+3@example.com"
],
"experience_jobs": [
{
"id": 999,
"label": "職歴なし"
}
]
},
{
"id": 3,
"name": "野原 ひろし",
"name_kana": "ノハラ ヒロシ",
"job_change_count": 0,
"address": {
"id": 2,
"label": "埼玉県"
},
"email": [
"test+4@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
}
]
}
]' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 768 100 14 100 754 1555 83777 --:--:-- --:--:-- --:--:-- 96000
{
"updateId": 1
}updateIdが1つしか返ってきていませんが、2つのオブジェクトが登録されていました。
恐らく返ってきているのはジョブのidかな?
まあ、無事に登録できました🎉
searchしてみる
ドキュメントを検索してみましょう。
searchエンドポイントのドキュメントはこちら。 https://docs.meilisearch.com/reference/api/search.html
どうやらPOSTで取得することを推奨しているのでPOSTで試します。
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "スネ夫"
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 624 100 598 100 26 116k 5200 --:--:-- --:--:-- --:--:-- 121k
{
"hits": [
{
"id": 2,
"name": "骨川 スネ夫",
"name_kana": "ホネカワ スネヲ",
"job_change_count": 0,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+3@example.com"
],
"experience_jobs": [
{
"id": 999,
"label": "職歴なし"
}
]
},
{
"id": 1,
"name": "鈴木 一郎",
"name_kana": "スズキ イチロウ",
"job_change_count": 2,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+1@example.com",
"test+2@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
},
{
"id": 200,
"label": "経営企画
}
]
}
],
"nbHits": 2,
"exhaustiveNbHits": false,
"query": "スネ夫",
"limit": 20,
"offset": 0,
"processingTimeMs": 1
}"検索文字列を入れて検索するだけならすごく簡単ですね。
曖昧検索はこんな感じですね。何も設定しなくて日本語検索できるのか。すごい。
細かな調整などは必要になってくるとは思いますが、一旦はこんな感じでおk。
ただ、完全一致で検索したい場合はどうするのでしょうか?
Filterを利用すれば良いようです。
https://docs.meilisearch.com/reference/features/filtering_and_faceted_search.html
Filterを利用する流れは、以下のようです。
- indexに
filterableAttributesを追加しておく。 - search時にfilterパラメタを設定して検索する。
簡単ですね。
現状ではネストされた配列とオブジェクトのフィルタリングはサポートしていないとのことです。
ここら辺はindexを設計するときに考慮するポイントになりますね。
なのでaddressやexperience_jobsなどはフィルタリング不可になります。
フィルタしたいのであればデータの持ち方を変える必要がありますね。
早速試してみましょう。
filterableAttributesの追加
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"filterableAttributes": [
"name",
"name_kana",
"email"
]
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 102 100 14 100 88 2000 12571 --:--:-- --:--:-- --:--:-- 14571
{
"updateId": 2
}filterパラメタを指定して検索
名前で完全一致検索する場合
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"filter": "name = \"骨川 スネ夫\""
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 366 100 317 100 49 63400 9800 --:--:-- --:--:-- --:--:-- 73200
{
"hits": [
{
"id": 2,
"name": "骨川 スネ夫",
"name_kana": "ホネカワ スネヲ",
"job_change_count": 0,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+3@example.com"
],
"experience_jobs": [
{
"id": 999,
"label": "職歴なし"
}
]
}
],
"nHits": 1,
"exhaustiveNbHits": false,
"query": "",
"limit": 20,
"offset": 0,
"processingTimeMs": 0
}OR検索する場合
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"filter": "name = \"野原 ひろし\" OR name = \"鈴木 一郎\""
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 667 100 589 100 78 143k 19500 --:--:-- --:--:-- --:--:-- 162k
{
"hits": [
{
"id": 1,
"name": "鈴木 一郎",
"name_kana": "スズキ イチロウ",
"job_change_count": 2,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+1@example.com",
"test+2@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
},
{
"id": 200,
"label": "経営企画"
}
]
},
{
"id": 3,
"name": "野原 ひろし",
"name_kana": "ノハラ ヒロシ",
"job_change_count": 0,
"address": {
"id": 2,
"label": "埼玉県"
},
"email": [
"test+4@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
}
]
}
],
"nbHits": 2,
"exhaustiveNbHits": false,
"query": "",
"limit": 20,
"offset": 0,
"processingTimeMs": 0
}配列の中を検索する場合
➜ ~ curl \
-X POST 'http://localhost:7700/indexes/users/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"filter": "email = \"test+2@example.com\""
}' | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 425 100 372 100 53 93000 13250 --:--:-- --:--:-- --:--:-- 103k
{
"hits": [
{
"id": 1,
"name": "鈴木 一郎",
"name_kana": "スズキ イチロウ",
"job_change_count": 2,
"address": {
"id": 1,
"label": "東京都"
},
"email": [
"test+1@example.com",
"test+2@example.com"
],
"experience_jobs": [
{
"id": 100,
"label": "営業(toB)"
},
{
"id": 200,
"label": "経営企画"
}
]
}
],
"nbHits": 1,
"exhaustiveNbHits": false,
"query": "",
"limit": 20,
"offset": 0,
"processingTimeMs": 0
}他にも諸々試しましたがfilterで=を利用すれば完全一致で検索できますね。
ドキュメントを読むと他にもいくつもルールがあります。
ざっとした使い方はこんなもんかなと。
終わりに
シンプルですね。
凝ったことをする時に物足りなくなるかもしれませんが、基本的な機能は備えていると思います。
曖昧検索についてどれくらい設定出来るかはもう少し調ベたいですが、好印象です。
Elasticsearchは多機能な分、選択肢が多いと思っているのでこのくらいシンプルに使えるの嬉しいです。