全文検索OSS比較:Meilisearch vs Typesense vs OpenSearch でAlgolia代替をセルフホスト
オープンソースラボ編集部 ・ 2026年6月13日
全文検索OSS比較:Meilisearch vs Typesense vs OpenSearch でAlgolia代替をセルフホスト
Algoliaは月$50〜(10万レコード・100万オペレーション)で、サイトの成長とともにコストが急増します。Meilisearch(Rust製・超高速・簡単)・Typesense(C++製・低レイテンシー)・OpenSearch(Elasticsearch fork・大規模対応)はOSSの全文検索エンジンで、Eコマース・ドキュメント検索・SaaS内検索などでAlgolia代替として使えます。
全文検索エンジンの選定理由
- コスト削減: Algoliaの月$5,000〜をVPS $10/月のMeilisearchで代替
- インスタント検索: タイピングのたびに結果が変わるライブ検索(デバウンス不要な低レイテンシー)
- タイポ許容: 「meilserach」→「meilisearch」のような入力ミスを自動補正
- ファセット検索: カテゴリ・価格帯・評価での絞り込みをUI付きで実装
- 日本語対応: ひらがな/カタカナ/漢字の全文検索・読み仮名検索
主要ツールの概要
Meilisearch
2018年からRustで開発されている全文検索エンジンです。GitHubスター47k+。「10行で使い始められる」のが特徴で、REST APIが直感的・セットアップが簡単・タイポ許容・ハイライト・ファセット・地理検索がデフォルトで使えます。日本語対応も早く、Next.jsでのインスタント検索実装が最も簡単です。
# MeilisearchをDockerで起動
docker run -d --name meilisearch -p 7700:7700 -e MEILI_MASTER_KEY=your-master-key -v /path/to/meili_data:/meili_data getmeili/meilisearch:v1.8
# APIキーの確認(マスターキーでアクセス)
curl http://localhost:7700/health
# => {"status":"available"}
# インデックスを作成してドキュメントを追加
curl -X POST 'http://localhost:7700/indexes/products/documents' -H 'Authorization: Bearer your-master-key' -H 'Content-Type: application/json' --data '[
{"id": 1, "name": "MacBook Pro M4", "category": "laptop", "price": 248800, "rating": 4.8},
{"id": 2, "name": "iPad Air M2", "category": "tablet", "price": 74800, "rating": 4.6},
{"id": 3, "name": "AirPods Pro", "category": "audio", "price": 39800, "rating": 4.7}
]'
// Next.js App Router + Meilisearch でインスタント検索を実装
// npm install meilisearch
// lib/meilisearch.ts
import { MeiliSearch } from 'meilisearch';
export const searchClient = new MeiliSearch({
host: process.env.NEXT_PUBLIC_MEILISEARCH_URL!,
apiKey: process.env.NEXT_PUBLIC_MEILISEARCH_API_KEY!, // 検索専用キー
});
// app/search/page.tsx
'use client';
import { useState, useEffect } from 'react';
import { searchClient } from '@/lib/meilisearch';
export default function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [filters, setFilters] = useState({ category: '', maxPrice: 100000 });
useEffect(() => {
const search = async () => {
if (!query) return;
const { hits } = await searchClient.index('products').search(query, {
limit: 20,
filter: [
filters.category ? `category = "${filters.category}"` : null,
`price <= ${filters.maxPrice}`,
].filter(Boolean),
facets: ['category', 'rating'],
attributesToHighlight: ['name'],
highlightPreTag: '<mark class="bg-yellow-200">',
highlightPostTag: '</mark>',
typoTolerance: { enabled: true, minWordSizeForTypos: { oneTypo: 4 } },
});
setResults(hits);
};
const debounce = setTimeout(search, 150);
return () => clearTimeout(debounce);
}, [query, filters]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="商品を検索..."
className="w-full border rounded px-4 py-2"
/>
<div className="grid grid-cols-3 gap-4 mt-4">
{results.map((hit: any) => (
<div key={hit.id} className="border rounded p-4">
<h3 dangerouslySetInnerHTML={{ __html: hit._formatted.name }} />
<p>¥{hit.price.toLocaleString()}</p>
</div>
))}
</div>
</div>
);
}
// Meilisearchのインデックス設定(日本語対応)
// scripts/setup-meilisearch.ts
import { MeiliSearch } from 'meilisearch';
const client = new MeiliSearch({ host: process.env.MEILISEARCH_URL!, apiKey: process.env.MEILISEARCH_MASTER_KEY! });
// インデックス設定
await client.index('products').updateSettings({
searchableAttributes: ['name', 'description', 'tags'],
filterableAttributes: ['category', 'price', 'rating', 'in_stock'],
sortableAttributes: ['price', 'rating', 'created_at'],
faceting: {
maxValuesPerFacet: 50,
},
typoTolerance: {
enabled: true,
minWordSizeForTypos: { oneTypo: 4, twoTypos: 8 },
},
pagination: { maxTotalHits: 1000 },
// 日本語の読み仮名検索をサポート
dictionary: ['MacBook', 'iPad', 'AirPods'],
});
// 検索専用APIキーを生成(フロントエンドに渡す)
const searchKey = await client.generateTenantToken(
process.env.MEILISEARCH_SEARCH_API_KEY_UID!,
{ indexes: [{ indexUid: 'products', rules: ['search'] }] }
);
console.log('Search API Key:', searchKey);
Typesense
2019年からC++で開発されている全文検索エンジンです。GitHubスター20k+。Algolia互換のAPIを持ち(typesense-instantsearch-adapterでAlgoliaのInstantSearch.jsが流用可能)、メモリ使用量が非常に少なく(Meilisearchの1/3以下)レイテンシーが一貫して低いです。マルチノードクラスター対応でHA(高可用性)構成が簡単です。
# Typesenseをdocker-composeで起動(シングルノード)
version: "3"
services:
typesense:
image: typesense/typesense:0.26.0
restart: unless-stopped
ports:
- "8108:8108"
volumes:
- typesense_data:/data
command: '--data-dir /data --api-key=your-api-key --enable-cors'
volumes:
typesense_data:
// TypesenseでAlgolia InstantSearch.jsを流用する
// npm install typesense typesense-instantsearch-adapter instantsearch.js
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter';
import instantsearch from 'instantsearch.js';
import { searchBox, hits, refinementList, stats, pagination } from 'instantsearch.js/es/widgets';
const typesenseAdapter = new TypesenseInstantSearchAdapter({
server: {
apiKey: process.env.NEXT_PUBLIC_TYPESENSE_SEARCH_KEY!,
nodes: [{ host: 'search.yoursite.com', port: 443, protocol: 'https' }],
},
additionalSearchParameters: {
queryBy: 'name,description,tags',
sortBy: '_text_match:desc,rating:desc',
numTypos: 1,
typoTokensThreshold: 1,
},
});
const search = instantsearch({
indexName: 'products',
searchClient: typesenseAdapter.searchClient,
});
search.addWidgets([
searchBox({ container: '#search-box', placeholder: '商品を検索...' }),
hits({ container: '#hits', templates: {
item: (hit) => `<div><h3>${hit._highlightResult.name.value}</h3><p>¥${hit.price}</p></div>`,
}}),
refinementList({ container: '#category-filter', attribute: 'category' }),
stats({ container: '#stats' }),
pagination({ container: '#pagination' }),
]);
search.start();
OpenSearch
ElasticsearchのBSL移行(2021年)を受けてAWSが主導でフォークしたOSSです。GitHubスター9.5k+。Elasticsearchと高い互換性を持ち、大規模(数億件以上)のドキュメント検索・複雑なアナリティクス・ログ解析に適しています。ベクター検索(k-NN)にも対応しており、セマンティック検索との組み合わせが可能です。
# OpenSearch docker-compose(開発用・シングルノード)
version: "3"
services:
opensearch:
image: opensearchproject/opensearch:2.13
restart: unless-stopped
environment:
- discovery.type=single-node
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=Admin@1234!
- DISABLE_SECURITY_PLUGIN=false
ports:
- "9200:9200"
- "9600:9600"
volumes:
- opensearch_data:/usr/share/opensearch/data
opensearch-dashboards:
image: opensearchproject/opensearch-dashboards:2.13
ports:
- "5601:5601"
environment:
OPENSEARCH_HOSTS: '["https://opensearch:9200"]'
depends_on:
- opensearch
volumes:
opensearch_data:
// OpenSearch TypeScript クライアント
// npm install @opensearch-project/opensearch
import { Client } from '@opensearch-project/opensearch';
const client = new Client({
node: process.env.OPENSEARCH_URL!,
auth: {
username: 'admin',
password: process.env.OPENSEARCH_PASSWORD!,
},
ssl: { rejectUnauthorized: false },
});
// 日本語対応のインデックス作成
await client.indices.create({
index: 'articles',
body: {
settings: {
analysis: {
analyzer: {
japanese: {
type: 'custom',
tokenizer: 'kuromoji_tokenizer', // analysis-kuromoji プラグイン必要
filter: ['kuromoji_baseform', 'lowercase'],
},
},
},
},
mappings: {
properties: {
title: { type: 'text', analyzer: 'japanese' },
content: { type: 'text', analyzer: 'japanese' },
published_at: { type: 'date' },
category: { type: 'keyword' },
embedding: { // ベクター検索(セマンティック検索)
type: 'knn_vector',
dimension: 1536, // OpenAI text-embedding-3-small
method: { name: 'hnsw', space_type: 'cosinesimil' },
},
},
},
},
});
// ハイブリッド検索(キーワード + ベクター)
const result = await client.search({
index: 'articles',
body: {
query: {
hybrid: {
queries: [
{ match: { content: { query: '機械学習', boost: 1.0 } } },
{ knn: { embedding: { vector: await getEmbedding('機械学習'), k: 10 } } },
],
},
},
},
});
機能比較表
| 比較項目 | Meilisearch | Typesense | OpenSearch |
|---|---|---|---|
| ライセンス | SSPL(v1.6+) | GPL-3 | Apache-2.0 |
| 言語 | Rust | C++ | Java |
| 最小RAM | 256MB | 64MB | 1GB+ |
| 日本語対応 | ✅ | 設定要 | ✅ プラグイン |
| タイポ許容 | ✅ | ✅ | 設定要 |
| ベクター検索 | ✅ | ✅ | ✅ k-NN |
| Algolia互換API | ❌ | ✅ アダプター | ❌ |
| スケール | 小〜中 | 中 | 大 |
| GitHub Stars | 47k+ | 20k+ | 9.5k+ |
検索エンジンのインフラはDevOpsカテゴリ/categories/devopsの他のツールと組み合わせて運用します。LLMを使ったセマンティック検索の実装はAIカテゴリ/categories/llm-toolsで解説しています。
FAQ
Q. Next.js App RouterのServer Componentで検索はどう実装すればよいですか?
A. Server ComponentでMeilisearchを使う場合、APIキーをサーバー側のみで使用でき安全です。実装パターン:
// app/search/page.tsx (Server Component)
// URLのsearchParamsで検索クエリを受け取る
export default async function SearchPage({
searchParams,
}: {
searchParams: { q?: string; category?: string; page?: string };
}) {
const query = searchParams.q || '';
const category = searchParams.category;
const page = parseInt(searchParams.page || '1') - 1;
// サーバーサイドでMeilisearchを直接呼び出し
const { MeiliSearch } = await import('meilisearch');
const client = new MeiliSearch({
host: process.env.MEILISEARCH_URL!,
apiKey: process.env.MEILISEARCH_MASTER_KEY!, // サーバーでのみ使用
});
const { hits, facetDistribution, totalHits } = await client
.index('products')
.search(query, {
hitsPerPage: 20,
page,
filter: category ? `category = "${category}"` : undefined,
facets: ['category', 'price_range'],
});
return (
<div>
<SearchInput defaultValue={query} /> {/* Client Component */}
<p>{totalHits}件の結果</p>
<ProductGrid hits={hits} />
<Pagination total={totalHits} perPage={20} current={page + 1} />
</div>
);
}
インクリメンタル検索(タイピング中に即時更新)はClient Componentが必要ですが、初期ロードはServer Componentで返すハイブリッドが最適です。
Q. MeilisearchとTypesenseのインポート(大量データ投入)速度を比較するとどうですか?
A. ベンチマーク(1億件のドキュメント・1レコード1KB)での参考値: Meilisearch: インデックス速度 約10万件/秒・100GB SSDを使用。Typesense: インデックス速度 約15万件/秒・消費メモリ2GB(Meilisearchより低メモリ)。大量データ投入のベストプラクティス: ①バッチサイズは1,000〜10,000件(1万件以上は効果が薄い)②インデックス中は検索可能(Meilisearch v1.xはインデックスが完了した時点でのみ検索可能→v1.10+で改善)③updateSettingsでsearchableAttributes・filterableAttributesを先に設定してからデータ投入。Supabaseとの同期: データ変更時にWebhookを使ってリアルタイムでMeilisearchを更新するパターンが一般的です(Supabase Realtime → Edge Function → Meilisearch)。
Q. OpenSearchで日本語全文検索を正しく設定する方法は?
A. OpenSearchはデフォルトで日本語の形態素解析をサポートしていません。analysis-kuromojiプラグインをインストールする必要があります。
# kuromoji プラグインのインストール
docker exec -it opensearch bin/opensearch-plugin install analysis-kuromoji
# カスタムアナライザーの設定(インデックス作成時)
curl -X PUT https://localhost:9200/articles -H 'Content-Type: application/json' -d '{
"settings": {
"analysis": {
"tokenizer": {
"kuromoji": {"type": "kuromoji_tokenizer", "mode": "search"}
},
"filter": {
"katakana_readingform": {"type": "kuromoji_readingform"},
"pos_filter": {"type": "kuromoji_part_of_speech", "stoptags": ["助詞", "助動詞"]}
},
"analyzer": {
"japanese": {
"tokenizer": "kuromoji",
"filter": ["kuromoji_baseform", "pos_filter", "lowercase"]
}
}
}
}
}'
mode: searchは長い複合語(「東京都知事」→「東京」「都」「知事」「東京都知事」)に分割します。mode: normalは単語境界のみ分割します。検索クエリと同じアナライザーを使うことが重要です。
Q. AlgoliaからMeilisearchへの移行手順を教えてください。
A. 移行は4ステップで行います。①データ移行: Algolia APIからインデックスのレコードをエクスポート(algoliasearchのbrowseAll()で全件取得)→Meilisearch APIにインポート②設定移行: AlgoliaのcustomRanking・searchableAttributes・facetsをMeilisearchの対応する設定に変換③SDK差し替え: algoliasearch → meilisearch パッケージに変更。APIの構造が異なるので呼び出し方を修正④フロントエンド: react-instantsearchはMeilisearch用の@meilisearch/react-instantsearchに差し替え(またはネイティブMeilisearch SDKで自前実装)。注意点: AlgoliaのRules(クエリルール・Virtual Browse等)はMeilisearchにはないため、カスタムフィルタロジックで代替が必要です。
まとめ
| ユースケース | 推奨ツール |
|---|---|
| 簡単に始めたい・Algolia代替 | Meilisearch |
| 低メモリ・高性能・Algolia互換API | Typesense |
| 大規模・ログ解析・ベクター検索 | OpenSearch |
| Next.jsのサイト内検索 | Meilisearch |