メタップスアドベントカレンダー4日目の記事です。
最近OpenSearchと格闘しています。
OpenSearchとは?
OpenSearchとは、高速な検索エンジンです。 ログ分析やサイト内の検索機能の実装など、結構いろんなことができるみたいです。
ドキュメントDBにようにJSON形式のデータをインデックス内に格納します。
インデックス: 従来のDBで言うところの『テーブル』
ドキュメント: 従来のDBで言うところの『レコード』
OpenSearchとElasticSearchの違い
検索エンジンとして有名なツールにElasticSearchがあります。両者は使い方が非常に似ているのですが、それもそのはず。OpenSearchはElasticsearchからForkされる形で誕生しました。
その理由はライセンス問題です。Elastic Searchは有名な検索エンジンで、過去にOSSとして公開されていましたが、商用サービス化を制限するライセンスへ変更することを発表しました。OSSとしての使用を続けるべくAmazon主導でForkされました。OpenSearchとElasticSearchはその歴史的な経緯から似ていますが、versionが上がるにつれて、独自の機能や記法が追加されています。とはいっても、同じような感覚で使えるのではないでしょうか。私はOpenSearchで開発をしていても、ElasticSearchの記事を読んだりしています。
OpenSearchの環境構築をしてみる
OpenSearchはDockerで簡単に環境構築が可能です。
以下のDocker構成で試してみてくだされ
※ volumeは貼ってないので、必要なら追加してください。都度passwordを入力するのが面倒なため、セキュリティプラグインを無効化しています。
version: "3.8"
services:
opensearch:
image: opensearchproject/opensearch:latest
container_name: opensearch
environment:
- DISABLE_SECURITY_PLUGIN=true
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
- cluster.name=opensearch-cluster
- node.name=opensearch-node
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
ports:
- 9200:9200
- 9600:9600
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:latest
container_name: opensearch-dashboards
environment:
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
- OPENSEARCH_HOSTS=http://opensearch:9200
ports:
- 5601:5601
depends_on:
- opensearch
簡単!
$ docker compose up -d
OpenSearchの操作方法
OpenSearchはHTTPベースでインデックス作成、データの投入、検索等が可能です。
curlコマンドやpostmanといった馴染みのツールで進めることもできますが、付属の専用dashbordを使うと便利です。
http://localhost:5601
でdashboardを開くことができます
OpenSearch『インデックス』作成
今回は求人サイトの検索機能を作成する前提で『インデックス』を作成します。
インデックスは従来のDBでいうところのテーブルです。
以下をそのままダッシュボードに貼り付けて、右上のsend Rqeustボタンを押してください
PUT jobs
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"status": {
"type": "keyword"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd"
},
"updated_at": {
"type": "date",
"format": "yyyy-MM-dd"
},
"employee": {
"properties": {
"full_time": {
"type": "integer"
},
"freelance": {
"type": "integer"
}
}
},
"positon": {
"type": "keyword"
},
"stack": {
"type": "nested",
"properties": {
"type": {
"type": "keyword"
},
"skill": {
"type": "keyword"
}
}
},
"salary": {
"properties": {
"basic": {
"type": "integer"
},
"benefits": {
"type": "integer"
}
}
}
}
}
}
以下のようなレスポンスが返ってきます
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "jobs"
}
作成したindex構造を確認
GET jobs/_mapping
データの追加
opensearchはデータを一括でindexに追加する際は以下のような構成にする必要があります。
// データは改行なしにする必要があります。
{"index":{}}
{データ}
{"index":{}}
{データ}
{"index":{}}
{データ}
JSONの整形や操作にはjqがおすすめです。
以下をdashbordに貼り付けて実行
POST jobs/_bulk
{"index": {}}
{"id":"XJB8431","status":"active","created_at":"2024-04-12","updated_at":"2024-09-23","employee":{"full_time":30,"freelance":10},"positon":"backend","stack":[{"type":"backend","skill":"Rails"},{"type":"frontend","skill":"React"}],"salary":{"basic":40,"benefits":5}}
{"index": {}}
{"id":"XJF9652","status":"active","created_at":"2024-03-15","updated_at":"2024-09-20","employee":{"full_time":50,"freelance":15},"positon":"frontend","stack":[{"type":"frontend","skill":"Vue.js"},{"type":"frontend","skill":"TypeScript"},{"type":"backend","skill":"Node.js"}],"salary":{"basic":45,"benefits":8}}
{"index": {}}
{"id":"XJM7123","status":"inactive","created_at":"2024-05-01","updated_at":"2024-09-25","employee":{"full_time":20,"freelance":25},"positon":"mobile","stack":[{"type":"mobile","skill":"React Native"},{"type":"mobile","skill":"Swift"},{"type":"backend","skill":"Firebase"}],"salary":{"basic":42,"benefits":6}}
indexの中身を確認
GET jobs/_search
検索
statusがactiveな求人のみを検索
GET jobs/_search
{
"query": {
"match": { "status": "active" }
}
}
求人作成日が2024-03-01 ~ 2024-04-31の間の求人を検索
GET jobs/_search
{
"query": {
"range": {
"created_at": {
"gte": "2024-03-01",
"lte": "2024-04-31"
}
}
}
}
stack.skillにReactがある求人を検索
mapping(index構造)でnestedしている場合は検索時もnestedであることを明示します
GET jobs/_search
{
"query": {
"nested": {
"path": "stack",
"query": {
"match": {
"stack.skill": "React"
}
}
}
}
}
求人がactiveでstack.typeにbackendがある(複数条件)
boolとfilterを使うのがポイント
GET jobs/_search
{
"query": {
"bool": {
"filter": [
{
"match": { "status": "active" }
},
{
"nested": {
"path": "stack",
"query": {
"match": {
"stack.type": "backend"
}
}
}
}
]
}
}
}
集計
OpenSearchは検索したドキュメントに対して集計処理をすることもできます。
求人のid別にtotal_incomeとしてsalary.basic + salary.benefitsしてみます
GET jobs/_search
{
"query": {
"match_all": {}
},
"aggs": {
"result": {
"terms": {
"field": "id"
},
"aggs": {
"total_income": {
"sum": {
"script": {
"source": "doc['salary.basic'].value + doc['salary.benefits'].value"
}
}
}
}
}
}
}
hits.hitsに検索対象のdocumentが、aggregationsに集計結果が格納されて返ってきます
終わりに
今回OpenSearchと格闘する上で有益だった記事を置いておきます。
先人達に感謝