dip Engineer Blog

Engineer Blog
ディップ株式会社のエンジニアによる技術ブログです。
弊社はバイトル・はたらこねっとなど様々なサービスを運営しています。

PHPを使ってOpensearchで日本語検索してみた

2021年11月に中途入社した砂原です。バイトルやはたらこねっとなどのHR系サービスの開発を担当している部署に所属しており、社内の技術的な課題や困りごとを調査・検証するようなお仕事をしています。

弊社では現在PostgreSQLを検索基盤として利用しているのですが、今後サイト内検索の利便性を高めていくためにPostgreSQLに変わるものはないかと調査・検証しています。

その中でも今回はOpenSearchを使ってみた系の話を書こうと思います。

OpenSearchについて

OpenSearchの詳しい解説はAWS様のドキュメントにも詳しく書いてあるので割愛します。

一般的にRDBはフリーワード検索や各種集計があまり得意ではないのですが、OpenSearchはその辺りの検索速度や利便性がかなり向上します。

また、Amazon OpenSearch Serviceを使えばかなり容易に検索基盤が構築できるので、もしRDBで検索機能を実現しているが「サイト内検索が遅い」「集計が遅い/もっといい感じの集計をしたい」などあれば、一度試してみる価値があると思います!

ということで、本記事はOpenSearchに触れる第一歩として、Dockerを用いて手元でOpenSearchを動かす手順を取り上げます。

バージョンは執筆時点(2022/6末)で最新が2.0.1なのですが、本記事では2.0.0を取り扱っています。

Dockerを使ってOpenSearchを立ち上げる

手元でサクッと動かす分には1ノードでも良いかと思うので、公式ドキュメントから少し変更しています。こちらはクラスメソッド様の記事を参考にさせていただきました。ありがとうございます!

version: '3'
services:
  opensearch:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: opensearch
    environment:
      - cluster.name=docker-cluster
      - node.name=opensearch-node
      - cluster.initial_master_nodes=opensearch-node
      - bootstrap.memory_lock=true
      - http.host=0.0.0.0
      - transport.host=127.0.0.1
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - $PWD/.data/opensearch:/usr/share/opensearch/data
    ports:
      - 9200:9200
    networks:
      - opensearch-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.0.0
    container_name: opensearch-dashboards
    environment:
      OPENSEARCH_HOSTS: "https://opensearch:9200"
    ports:
      - 5601:5601
    networks:
      - opensearch-net

volumes:
  opensearch-data:

networks:
  opensearch-net:

Dockerfile内で日本語検索に必要なpluginをインストールします。/tmpに一度ダウンロードしているのですが、直接インストールしてもいいかと思います。

FROM opensearchproject/opensearch:2.0.0

# Download Plugin Files
RUN curl -L -k -O https://artifacts.opensearch.org/releases/plugins/analysis-kuromoji/2.0.0/analysis-kuromoji-2.0.0.zip --output-DIR /tmp
RUN curl -L -k -O https://artifacts.opensearch.org/releases/plugins/analysis-icu/2.0.0/analysis-icu-2.0.0.zip --output-dir /tmp

# Install Plugin
RUN /usr/share/opensearch/bin/opensearch-plugin install file:///tmp/analysis-kuromoji-2.0.0.zip
RUN /usr/share/opensearch/bin/opensearch-plugin install file:///tmp/analysis-icu-2.0.0.zip

docker compose -d up を実行します。

これで準備は完了です!

OpenSearch環境の確認

Dashboard

http://localhost:5601/ にアクセスできればOKです。

なお、初期USER/PASSは admin/admin です。

OpenSearch

https://localhost:9200/ にアクセスし、以下のようなOpenSearchの情報が表示されればOKです。

こちらも初期USER/PASSは admin/admin です。

{
  "name" : "opensearch-node",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "2fGiy_Q2QsqIRIrfv6Luiw",
  "version" : {
    "distribution" : "opensearch",
    "number" : "2.0.0",
    "build_type" : "tar",
    "build_hash" : "bae3b4e4178c20ac24fece8e82099abe3b2630d0",
    "build_date" : "2022-05-19T00:26:04.115016552Z",
    "build_snapshot" : false,
    "lucene_version" : "9.1.0",
    "minimum_wire_compatibility_version" : "7.10.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}

次はPHPでOpenSearchを操作してみましょう。

OpenSearch-phpの利用方法

1.composerをインストールする

2.composerにopensearch-phpを追加する

composer require opensearch-project/opensearch-php

3.サンプルスクリプトを用意しました。こちらを実行して動けばOKです。
サンプルスクリプトの処理は以下の通りです。
prnameいうフィールドをもつtest-indexという名前のインデックスを作成(prはkuromojiを利用するよう設定)
prフィールドに対して京都というワードで全文検索
・検索結果を表示(東京都はHITせず、京都だけがHITする)

<?php
require __DIR__ . '/vendor/autoload.php';

// 環境によって適宜変更してください
$opensearchHost = [
    'host' => 'localhost',
    'scheme' => 'https',
    'port' => '9200'
];
$user = 'admin';
$pass = 'admin';
$indexName = 'test-index';

$client = (new \OpenSearch\ClientBuilder())
    ->setHosts([$opensearchHost])
    ->setBasicAuthentication($user, $pass)
    ->setSSLVerification(false) 
    ->build();


// index作成
$indexParams['index']  = $indexName;
$exists = $client->indices()->exists($indexParams);
if (!$exists) {
    $client->indices()->create([
        'index' => $indexName,
        'body' => [
            'settings' => [
                'index' => [
                    'analysis' => [
                        'analyzer' => [
                            'kuromoji' => [
                                'type' => 'custom',
                                'tokenizer' => 'kuromoji_tokenizer'
                            ]
                        ]
                    ]
                ]
            ],
            'mappings' => [
                'properties' => [
                    'pr' => [
                        'type' => 'text',
                        'analyzer' => 'kuromoji'
                    ],
                    'name' => [
                        'type' => 'text'
                    ]
                ]
            ]   
        ]   
    ]); 
}

// データ作成
$client->create([
    'index' => $indexName,
    'id' => 1,
    'body' => [
        'name' => '田中一郎',
        'pr' => '京都に10年住んでいます。'
    ]
]);

$client->create([
    'index' => $indexName,
    'id' => 2,
    'body' => [
        'name' => '山田花子',
        'pr' => '東京都出身です。'
    ]
]);


// データ検索
$result =(
    $client->search([
        'index' => $indexName,
        'body' => [
            '_source' => ['name','pr'],
            'query' => [
                'match' => [
                    'pr' => '京都'
                ]   
            ]   
        ]   
    ])  
);

var_dump($result)

※環境によってはデータ作成後にsleepを数秒入れないと正しく結果が取得できないかもしれません。

以上がDockerを用いて手元でOpenSearchを動かす手順となります。

最後に

OpenSearchはElasticsearch7.10.2から派生したという経緯があるため、もし実装にあたり困ったことが発生した場合はElasticsearch-PHPのドキュメントも参考にすると答えが見つかるかもしれません。

検証時にOpenSearch×PHPのサンプルがなかなか見つからず苦労したので、記事にしてみました。お役に立てると嬉しいです。