初めてのOpenSearch入門

メタップスアドベントカレンダー4日目の記事です。

最近OpenSearchと格闘しています。

## OpenSearchとは?


OpenSearchとは、高速な検索エンジンです。 ログ分析やサイト内の検索機能の実装など、結構いろんなことができるみたいです。
ドキュメントDBにようにJSON形式のデータをインデックス内に格納します。

インデックス: 従来のDBで言うところの『テーブル』
ドキュメント: 従来のDBで言うところの『レコード』

https://opensearch.org/
## OpenSearchとElasticSearchの違い
検索エンジンとして有名なツールにElasticSearchがあります。両者は使い方が非常に似ているのですが、それもそのはず。OpenSearchはElasticsearchからForkされる形で誕生しました。
その理由はライセンス問題です。Elastic Searchは有名な検索エンジンで、過去にOSSとして公開されていましたが、商用サービス化を制限するライセンスへ変更することを発表しました。OSSとしての使用を続けるべくAmazon主導でForkされました。OpenSearchとElasticSearchはその歴史的な経緯から似ていますが、versionが上がるにつれて、独自の機能や記法が追加されています。とはいっても、同じような感覚で使えるのではないでしょうか。私はOpenSearchで開発をしていても、ElasticSearchの記事を読んだりしています。


## OpenSearchの環境構築をしてみる
OpenSearchはDockerで簡単に環境構築が可能です。
以下のDocker構成で試してみてくだされ
※ volumeは貼ってないので、必要なら追加してください。都度passwordを入力するのが面倒なため、セキュリティプラグインを無効化しています。
```yml
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
```

簡単!
```shell
$ docker compose up -d
```

## OpenSearchの操作方法
OpenSearchはHTTPベースでインデックス作成、データの投入、検索等が可能です。
curlコマンドやpostmanといった馴染みのツールで進めることもできますが、付属の専用dashbordを使うと便利です。
`http://localhost:5601`でdashboardを開くことができます 

開いたら右上のDev Toolsを開いてください
![名称未設定のデザイン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1390680/43145435-426a-ad6f-4779-d55f472b1a18.png)



## OpenSearch『インデックス』作成
今回は求人サイトの検索機能を作成する前提で『インデックス』を作成します。
インデックスは従来のDBでいうところのテーブルです。

以下をそのままダッシュボードに貼り付けて、右上のsend Rqeustボタンを押してください
```ts
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に追加する際は以下のような構成にする必要があります。


```js
// データは改行なしにする必要があります。
{"index":{}}
{データ}
{"index":{}}
{データ}
{"index":{}}
{データ}
```
JSONの整形や操作にはjqがおすすめです。

https://jqlang.github.io/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と格闘する上で有益だった記事を置いておきます。
先人達に感謝


https://dev.classmethod.jp/articles/elasticsearch-starter5/

https://dev.classmethod.jp/articles/elasticsearch-getting-started-02/#toc-nested--object-

https://tkrtkhsh.hatenablog.com/entry/2017/12/02/234409