データベース

MeiliSearch

概要

MeiliSearchは、Rust言語で開発された軽量で高速な全文検索エンジンです。特に優れたタイポ耐性と50ミリ秒以下の検索応答速度を誇り、RESTful APIによる簡単な操作が可能です。セルフホスト可能なオープンソースソフトウェアとして、日本語を含む多言語に最適化されており、開発者にとって扱いやすい検索ソリューションを提供します。

詳細

MeiliSearchは、フランス・パリのMeilisearch社によって開発された次世代検索エンジンです。Rust言語の安全性と高性能を活かし、従来の検索エンジンの課題を解決することを目的としています。2020年にオープンソース化され、2023年7月に安定版v1.0がリリースされました。

MeiliSearchの主な特徴:

  • 超高速検索: 平均4ミリ秒の検索応答時間
  • 強力なタイポ耐性: 誤字脱字があっても適切な検索結果を返す
  • 軽量設計: 低メモリ使用量で動作
  • 多言語サポート: 日本語形態素解析器Lindera内蔵
  • RESTful API: シンプルで直感的なAPI設計
  • ファセット検索: 属性による絞り込み検索
  • 地理的検索: 位置情報ベースの検索
  • ランキングカスタマイズ: 検索結果の順位付けルール調整
  • シノニム機能: 類義語による検索拡張
  • ハイライト機能: 検索キーワードの強調表示
  • ベクトル検索: セマンティック検索(実験的機能)
  • マルチテナンシー: ユーザー別アクセス制御

メリット・デメリット

メリット

  • 優秀なタイポ耐性: 入力ミスがあっても期待する結果を返す
  • 超高速検索: 4ミリ秒の応答時間で即座に結果表示
  • 軽量リソース: 少ないメモリとCPUで動作
  • 簡単セットアップ: Dockerで5分以内に構築可能
  • 優れたUX: Typeahead検索やオートコンプリート対応
  • 日本語最適化: Lindera形態素解析器による高精度日本語検索
  • 開発者フレンドリー: 直感的なAPIと豊富なクライアントライブラリ
  • オープンソース: 無料で商用利用可能
  • アクティブな開発: 継続的な機能改善とコミュニティサポート

デメリット

  • 比較的新しい技術: Elasticsearchと比べて実績が少ない
  • エンタープライズ機能制限: 高度なクラスタリング機能は限定的
  • プラグインエコシステム: Elasticsearchほど豊富なプラグインがない
  • 分散処理制限: 大規模分散環境での運用は課題
  • 複雑な分析機能: Elasticsearchの集約機能ほど高度ではない
  • バックアップツール: エンタープライズレベルのバックアップ機能が限定的

主要リンク

書き方の例

Docker での実行

# MeiliSearchのDockerコンテナを起動
docker run -d \
  --name meilisearch \
  -p 7700:7700 \
  -e MEILI_ENV=development \
  -v $(pwd)/meili_data:/meili_data \
  getmeili/meilisearch:v1.3.0

# マスターキーを指定した本番環境での起動
docker run -d \
  --name meilisearch \
  -p 7700:7700 \
  -e MEILI_ENV=production \
  -e MEILI_MASTER_KEY=your-master-key-here \
  -v $(pwd)/meili_data:/meili_data \
  getmeili/meilisearch:v1.3.0

# 起動確認
curl http://localhost:7700/health

インデックス作成とドキュメント追加

# インデックス作成
curl -X POST 'http://localhost:7700/indexes' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "uid": "movies",
    "primaryKey": "id"
  }'

# 複数ドキュメントの一括追加
curl -X POST 'http://localhost:7700/indexes/movies/documents' \
  -H 'Content-Type: application/json' \
  --data-binary '[
    {
      "id": 1,
      "title": "スパイダーマン",
      "genre": ["アクション", "冒険"],
      "director": "サム・ライミ",
      "release_year": 2002,
      "rating": 7.3,
      "overview": "平凡な高校生ピーター・パーカーがスパイダーマンに変身する物語"
    },
    {
      "id": 2,
      "title": "アベンジャーズ",
      "genre": ["アクション", "SF"],
      "director": "ジョス・ウェドン",
      "release_year": 2012,
      "rating": 8.0,
      "overview": "地球最強のヒーローチームが世界を救う壮大な物語"
    }
  ]'

# 単一ドキュメントの追加
curl -X POST 'http://localhost:7700/indexes/movies/documents' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "id": 3,
    "title": "君の名は。",
    "genre": ["アニメ", "ロマンス"],
    "director": "新海誠",
    "release_year": 2016,
    "rating": 8.2,
    "overview": "時空を超えた恋愛を描く美しいアニメーション作品"
  }'

基本検索

# シンプル検索
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "スパイダー"
  }'

# タイポ耐性検索("スパイダー"を"スパイダ"で検索)
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "スパイダ"
  }'

# 制限とオフセット
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "アクション",
    "limit": 5,
    "offset": 0
  }'

# 特定の属性のみ返す
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "アベンジャーズ",
    "attributesToRetrieve": ["title", "director", "release_year"]
  }'

フィルタリング検索

# フィルタリング属性の設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "filterableAttributes": ["genre", "release_year", "rating", "director"]
  }'

# ジャンルによるフィルタリング
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "filter": "genre = アクション"
  }'

# 複数条件でのフィルタリング
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "filter": "release_year > 2010 AND rating >= 8.0"
  }'

# OR条件でのフィルタリング
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "filter": "director = \"新海誠\" OR director = \"サム・ライミ\""
  }'

# 配列要素での検索
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "filter": "genre IN [アニメ, SF]"
  }'

ソート機能

# ソート可能属性の設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "sortableAttributes": ["release_year", "rating", "title"]
  }'

# 評価順(降順)でソート
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "sort": ["rating:desc"]
  }'

# 複数条件でのソート
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "sort": ["release_year:desc", "rating:desc"]
  }'

# 検索クエリとソートの組み合わせ
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "アクション",
    "filter": "release_year >= 2000",
    "sort": ["rating:desc"]
  }'

ハイライト機能

# ハイライト属性の設定
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "スパイダーマン",
    "attributesToHighlight": ["title", "overview"],
    "highlightPreTag": "<mark>",
    "highlightPostTag": "</mark>"
  }'

# 全属性をハイライト
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "ヒーロー",
    "attributesToHighlight": ["*"]
  }'

ファセット検索

# ファセット分布の取得
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "facets": ["genre", "director"]
  }'

# 最大ファセット値の制限
curl -X POST 'http://localhost:7700/indexes/movies/search' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "q": "",
    "facets": ["genre"],
    "maxValuesPerFacet": 10
  }'

タイポ耐性設定

# タイポ耐性設定の確認
curl -X GET 'http://localhost:7700/indexes/movies/settings/typo-tolerance'

# タイポ耐性の無効化
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/typo-tolerance' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "enabled": false
  }'

# 最小文字数の設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/typo-tolerance' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "minWordSizeForTypos": {
      "oneTypo": 4,
      "twoTypos": 10
    }
  }'

# 特定の単語でタイポ耐性を無効化
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/typo-tolerance' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "disableOnWords": ["アベンジャーズ", "スパイダーマン"]
  }'

# 特定の属性でタイポ耐性を無効化
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/typo-tolerance' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "disableOnAttributes": ["director"]
  }'

ランキングルール設定

# ランキングルールの確認
curl -X GET 'http://localhost:7700/indexes/movies/settings/ranking-rules'

# カスタムランキングルールの設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/ranking-rules' \
  -H 'Content-Type: application/json' \
  --data-binary '[
    "words",
    "typo",
    "proximity",
    "attribute",
    "sort",
    "exactness",
    "rating:desc"
  ]'

# 検索可能属性の設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/searchable-attributes' \
  -H 'Content-Type: application/json' \
  --data-binary '[
    "title",
    "overview",
    "director",
    "genre"
  ]'

シノニム設定

# シノニムの設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/synonyms' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "映画": ["ムービー", "フィルム"],
    "アクション": ["バトル", "戦闘"],
    "SF": ["サイエンスフィクション", "未来"]
  }'

# シノニムの確認
curl -X GET 'http://localhost:7700/indexes/movies/settings/synonyms'

ストップワード設定

# ストップワードの設定
curl -X PATCH 'http://localhost:7700/indexes/movies/settings/stop-words' \
  -H 'Content-Type: application/json' \
  --data-binary '["の", "を", "に", "が", "は", "で", "と", "から"]'

# ストップワードの確認
curl -X GET 'http://localhost:7700/indexes/movies/settings/stop-words'

インデックス管理

# インデックス一覧取得
curl -X GET 'http://localhost:7700/indexes'

# インデックス情報取得
curl -X GET 'http://localhost:7700/indexes/movies'

# インデックス統計情報
curl -X GET 'http://localhost:7700/indexes/movies/stats'

# インデックス削除
curl -X DELETE 'http://localhost:7700/indexes/movies'

# 全設定の確認
curl -X GET 'http://localhost:7700/indexes/movies/settings'

# 全設定のリセット
curl -X DELETE 'http://localhost:7700/indexes/movies/settings'

JavaScriptクライアント

// MeiliSearchクライアントの初期化
import { MeiliSearch } from 'meilisearch'

const client = new MeiliSearch({
  host: 'http://localhost:7700',
  apiKey: 'your-api-key' // 本番環境では必須
})

// インデックス取得
const index = client.index('movies')

// ドキュメント追加
const documents = [
  {
    id: 1,
    title: '君の名は。',
    genre: ['アニメ', 'ロマンス'],
    director: '新海誠',
    release_year: 2016
  }
]

await index.addDocuments(documents)

// 検索実行
const searchResults = await index.search('君の名は', {
  attributesToHighlight: ['title'],
  filter: 'release_year > 2015',
  sort: ['release_year:desc'],
  limit: 10
})

console.log(searchResults.hits)

// タイポ耐性のある検索
const typoResults = await index.search('きみのなま') // 「君の名は」のタイポ
console.log(typoResults.hits)

// ファセット検索
const facetResults = await index.search('', {
  facets: ['genre', 'director']
})
console.log(facetResults.facetDistribution)

Python クライアント

# MeiliSearchクライアントの初期化
import meilisearch

client = meilisearch.Client('http://localhost:7700', 'your-api-key')
index = client.index('movies')

# ドキュメント追加
documents = [
    {
        'id': 1,
        'title': 'アベンジャーズ',
        'genre': ['アクション', 'SF'],
        'director': 'ジョス・ウェドン',
        'release_year': 2012,
        'rating': 8.0
    }
]

index.add_documents(documents)

# 検索実行
search_results = index.search('アベンジャーズ', {
    'filter': 'rating >= 8.0',
    'attributesToHighlight': ['title', 'overview'],
    'sort': ['rating:desc']
})

print(search_results['hits'])

# 設定更新
index.update_filterable_attributes(['genre', 'rating', 'release_year'])
index.update_sortable_attributes(['rating', 'release_year'])

# タイポ耐性設定
index.update_typo_tolerance({
    'minWordSizeForTypos': {
        'oneTypo': 4,
        'twoTypos': 10
    },
    'disableOnAttributes': ['director']
})

Go クライアント

package main

import (
    "fmt"
    "github.com/meilisearch/meilisearch-go"
)

func main() {
    // クライアント初期化
    client := meilisearch.NewClient(meilisearch.ClientConfig{
        Host:   "http://localhost:7700",
        APIKey: "your-api-key",
    })

    // インデックス取得
    index := client.Index("movies")

    // 検索実行
    searchRes, err := index.Search("スパイダーマン", &meilisearch.SearchRequest{
        Filter:   "release_year > 2000",
        Sort:     []string{"rating:desc"},
        Limit:    10,
        AttributesToHighlight: []string{"title", "overview"},
    })
    
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Found %d results\n", len(searchRes.Hits))
    
    // タイポ耐性設定更新
    _, err = index.UpdateTypoTolerance(&meilisearch.TypoTolerance{
        MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{
            OneTypo:  4,
            TwoTypos: 10,
        },
        DisableOnWords: []string{"アベンジャーズ"},
    })
    
    if err != nil {
        panic(err)
    }
}

パフォーマンス最適化

# インデックス最適化(定期実行推奨)
curl -X POST 'http://localhost:7700/indexes/movies/documents' \
  -H 'Content-Type: application/json' \
  --data-binary '[]'  # 空の配列でインデックスを最適化

# 設定のバッチ更新
curl -X PATCH 'http://localhost:7700/indexes/movies/settings' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "filterableAttributes": ["genre", "release_year", "rating"],
    "sortableAttributes": ["release_year", "rating"],
    "searchableAttributes": ["title", "overview", "director"],
    "displayedAttributes": ["title", "director", "release_year", "rating"],
    "rankingRules": [
      "words",
      "typo",
      "proximity",
      "attribute",
      "sort",
      "exactness",
      "rating:desc"
    ]
  }'