Laravelで数百万件のデータ検索が必要になりMeilisearchを使ってみた記録です。結論は思っていたよりも安く簡単に設定できるので最高でした。
この記事では全文検索エンジンの Meilisearch の紹介と、使うことになった理由、Laravelでの使い方を紹介していきます。
目次
全文検索エンジンのMeilisearchとは
全文検索エンジンのMeilisearchは、大量のテキストデータから特定のキーワードを効率的に見つけ出すための強力なツールです。
公式サイトはこちら
最初はLaravelの全文検索用のライブラリLaravel Scout で何が選択肢として選べるのかと読み込んでいたら
- Algolia
- Meilisearch
- MySQL / PostgreSQL
が使えると知ったのが最初です。
当時はMySQLで実装していましたが、流石に検索スピードが遅すぎるので Meilisearch に移行しました。
Algolia は全文検索としてとても人気があるツールである反面、Algoliaのアカウントを作成してAlgoliaのサーバーにデータ保存する必要があり、自分自身のプロジェクトならまだしも、お客様のデータを渡すのはなかなか手出し出来ませんでした。
AWS の Elasticsearch での実装経験もあったので、そちらの線で進めようかと思っていたのですが、Meilisearchはオープンソースの全文検索エンジンなので自社サーバーで稼働する事ができ、使いやすいRESTful API、JSONフォーマットでのデータの取り扱いなどが魅力的でした。
なによりも Laravel Scout のライブラリが Meilisearch の使い方で紹介していたのでサポートも充実しているかと期待して採用しました。
MySQLの全文検索だとダメなのか
Meilisearch での設定を紹介する前に、元々はMySQLでの全文検索を行っていました。
ユーザー情報の永続化など、Webアプリケーションを作成する中でMySQL等のデータベースエンジンは必ずと言って使います。このMySQLのままで全文検索を実現できれば必要コストが増えずに済みます。
ですが、MySQL等のデータベースエンジンはデータの保存や編集に特化しているため、IDなどの数値や文字列の有る無しを判別するのに特化しており、「この文章の中の特定の文字が含まれているか」を検索するのはあまり得意ではありません。
マシン性能やテーブル設計にもよりますが、数万件程度のデータセットまでは実用的な速度で実装することは出来ますが、数百万件レベルになるとデータベースエンジンへの負荷がかかりすぎてシステム稼働への弊害が出てきます。
検索機能が肝になるシステムでは致命傷となりえる事態なので、データが肥大化して検索パフォーマンスに問題が出てきたら全文検索エンジンを導入する事をオススメします。
LaravelでのMeilisearchの設定方法
ここからは Laravel Scout を使用した Meilisearch の設定方法です。
まずはVPS等でMeilisearchを稼働させるサーバーを用意します。自分は VPSのUbuntuサーバー、メモリ4GB、vCPU4コア、dockerで公開したかったので dokku サーバーを調達しました。
Laravel Scout の設定は軽くですが紹介しておきますと
composer require laravel/scout
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
public function toSearchableArray()
{
return [
'id' => (int) $this->id,
'name' => $this->name,
'price' => (float) $this->price,
];
}
}
$orders = Post::search('Star Trek')->get();
程度で使えるのでかなりお手軽でした。
Meilisearch サーバーの作成
dokkuサーバーで設定する方法です。
公式のプラグインとして用意されているのがあるので、こちらを使います。
https://github.com/dokku/dokku-meilisearch
基本方針としては
- docker内でMeilisearchを稼働
- 設定値などの再現性を高める
- dokkuで稼働させる事でDockerfileの管理も不要にさせる
です。
dokkuがセットアップ済みであれば3コマンドで設定完了になります。
dokku plugin:install https://github.com/dokku/dokku-meilisearch.git meilisearch
dokku meilisearch:create lollipop # lollipop は自由に設定できるサンプルアプリ名です
# Dsn: http://:<パスワード>@dokku-meilisearch-lollipop:7700
dokku meilisearch:expose lollipop
# [container->host]: 7700-><公開ポート>
上記のコメントで表示される<パスワード>と<公開ポート>はメモしておきます。
Laravelプロジェクトに戻って .env
にメモした内容を貼り付ければ使えるようになります。
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://<dokkuサーバーのIPアドレスやドメイン>:<公開ポート>
MEILISEARCH_KEY=<パスワード>
Meilisearchサーバーは以上です。
Meilisearchログの確認
dokku meilisearch:logs lollipop --tail
dokkuサーバーで上記で確認する事ができます。
Meilisearchの RESTful API をcurlで実行する事で実際の登録値を確認できます。
# 認証キーの確認。フロントと管理用のキーが閲覧できる
curl -X GET \
'http://<dokkuサーバーのIPアドレスやドメイン>:<公開ポート>/keys' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <パスワード>' | jq .
# インデックス名に登録された数確認 (管理用のキーで閲覧)
curl -X GET \
'http://<dokkuサーバーのIPアドレスやドメイン>:<公開ポート>/indexes/<インデックス名>/documents' \
-H 'Authorization: Bearer <管理用のキー>' \
| jq .
Laravel での設定
残りは検索用のインデックス作成やデータ登録を行います。
use App\Models\Post;
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
Post::class => [
'filterableAttributes'=> ['id', 'name', 'price'],
'sortableAttributes' => ['created_at'],
// Other settings fields...
],
],
],
# インデックス作成
php artisan scout:sync-index-settings
# データ登録
php artisan scout:import "App\Models\Post"
これで基本的には登録完了して、使い始める事ができます。データ更新が走ると自動的に更新用のリクエストが走ってくれます。
しかし、私が試した時はデータ量が多いのか登録漏れを確認したのでデータ登録用の専用コマンドを作成して対応しました。
public function handle()
{
$this->call('scout:sync-index-settings');
$model = Post::query();
$this->info('post max count:' . $model->count());
$model->chunkById(1000, function ($posts) {
$posts->searchable();
sleep(1);
});
return Command::SUCCESS;
}
今思うと SCOUT_QUEUE=true
などを使用してキューに貯めて間髪入れずに登録しまくっていたのが悪さしていたのかもしれません。
状況を確認しつつ臨機応変に対応してみてください。
最後に
以上、Laravelによる全文検索エンジンMeilisearchの設定方法でした。
MySQLでの全文検索だとかなり遅かったので、対応してみたところ簡単に対応することができました。参考になりましたら幸いです。
コメントを残す