Typesense

高速でタイポ耐性のあるオープンソース検索エンジン。インスタント検索体験、地理検索、ファセット検索をサポート。Elasticsearchの軽量代替。

Search EngineOpen SourceFast SearchReal-timeTypo ToleranceRESTfulDeveloper-friendlyVector Search

サーバー

Typesense

概要

Typesenseは、C++で構築された高速でデベロッパーフレンドリーなオープンソース検索エンジンです。1ミリ秒以下での検索応答、優秀なタイポ許容機能、ベクトル検索とハイブリッド検索をサポートし、セットアップが簡単で直感的なAPIを提供します。インメモリアーキテクチャによる超高速検索と、リアルタイムインデックス更新により、優れたユーザー体験を実現します。

詳細

Typesense 2024版では、ベクトル検索、ハイブリッド検索(ベクトル+キーワード)、カンバセーショナル検索、複数言語対応のタイポ許容機能、高度なフィルタリング、地理的検索、リアルタイム分析機能を搭載しています。C++による最適化されたアーキテクチャにより、ElasticsearchやSolrを大幅に上回る検索性能を実現し、開発者向けの直感的なREST APIとGraphQL支援により、迅速な実装が可能です。

主要機能

  • 超高速検索: インメモリアーキテクチャによる1ミリ秒以下の検索応答
  • AI機能: ベクトル検索とハイブリッド検索によるセマンティック検索
  • 強力なタイポ許容: 高度なスペル修正とファジーマッチング
  • リアルタイム: インデックスとクエリのリアルタイム処理
  • 多言語対応: 包括的な国際化サポートと言語固有の最適化
  • 地理的検索: 高性能なジオサーチとロケーションベースのフィルタリング

メリット・デメリット

メリット

  • シンプルなセットアップと直感的なAPIによる高い開発効率
  • ElasticsearchやSolrと比較して圧倒的に高速な検索性能
  • オープンソースによるコスト削減と技術的自由度
  • 小さなフットプリントと低いリソース消費での高性能
  • 包括的な検索機能(ファセット、フィルタリング、ソート)の簡単な実装
  • セルフホスト可能な完全なデータコントロール

デメリット

  • インメモリアーキテクチャによるメモリ使用量の増加
  • 大規模データセットでのElasticsearchほどの分散処理能力はない
  • プラグインエコシステムが他の成熟した検索エンジンより小さい
  • 複雑な分析クエリでElasticsearchに劣る場合がある
  • 商用サポートオプションが限定的
  • 大規模エンタープライズ向け機能が一部制約される場合がある

参考ページ

書き方の例

セットアップとインストール

# Dockerでの実行
docker run -p 8108:8108 -v /tmp/typesense-data:/data typesense/typesense:0.25.2 \
  --data-dir /data --api-key=xyz --enable-cors

# macOSでのインストール(Homebrew)
brew install typesense-server

# Linuxでのバイナリインストール
wget https://dl.typesense.org/releases/0.25.2/typesense-server-0.25.2-linux-amd64.tar.gz
tar -xzf typesense-server-0.25.2-linux-amd64.tar.gz
cd typesense-server-0.25.2-linux-amd64

# サーバー起動
./typesense-server --data-dir=/tmp/typesense-data --api-key=xyz --enable-cors

# Docker Composeでの設定
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  typesense:
    image: typesense/typesense:0.25.2
    ports:
      - "8108:8108"
    volumes:
      - ./typesense-data:/data
    command: '--data-dir /data --api-key=your-api-key --enable-cors'
    environment:
      - TYPESENSE_API_KEY=your-secure-api-key
      - TYPESENSE_DATA_DIR=/data
    restart: unless-stopped
EOF

docker-compose up -d

# Kubernetesでのデプロイ
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: typesense
spec:
  replicas: 3
  selector:
    matchLabels:
      app: typesense
  template:
    metadata:
      labels:
        app: typesense
    spec:
      containers:
      - name: typesense
        image: typesense/typesense:0.25.2
        ports:
        - containerPort: 8108
        env:
        - name: TYPESENSE_API_KEY
          valueFrom:
            secretKeyRef:
              name: typesense-secret
              key: api-key
        args:
        - --data-dir=/data
        - --api-key=$(TYPESENSE_API_KEY)
        - --enable-cors
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: typesense-pvc
EOF

コレクション作成とドキュメント管理

# コレクションスキーマ作成
curl -X POST "http://localhost:8108/collections" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "movies",
    "fields": [
      {"name": "id", "type": "string"},
      {"name": "title", "type": "string"},
      {"name": "overview", "type": "string"},
      {"name": "genre", "type": "string[]"},
      {"name": "release_date", "type": "int64"},
      {"name": "rating", "type": "float"},
      {"name": "director", "type": "string"},
      {"name": "duration", "type": "int32"},
      {"name": "location", "type": "geopoint"},
      {"name": "embedding", "type": "float[]", "num_dim": 384}
    ],
    "default_sorting_field": "rating"
  }'

# 単一ドキュメント追加
curl -X POST "http://localhost:8108/collections/movies/documents" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "1",
    "title": "千と千尋の神隠し",
    "overview": "宮崎駿監督による日本のアニメーション映画",
    "genre": ["アニメーション", "ファミリー", "ファンタジー"],
    "release_date": 995068800,
    "rating": 9.2,
    "director": "宮崎駿",
    "duration": 125,
    "location": [35.6762, 139.6503]
  }'

# 複数ドキュメントの一括追加(JSONL形式)
curl -X POST "http://localhost:8108/collections/movies/documents/import" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @- << 'EOF'
{"id": "2", "title": "君の名は。", "overview": "新海誠監督による青春ファンタジーアニメ", "genre": ["アニメーション", "ロマンス", "ドラマ"], "release_date": 1472169600, "rating": 8.4, "director": "新海誠", "duration": 106}
{"id": "3", "title": "鬼滅の刃 無限列車編", "overview": "吾峠呼世晴原作による人気アニメの映画版", "genre": ["アニメーション", "アクション", "歴史"], "release_date": 1602806400, "rating": 8.7, "director": "外崎春雄", "duration": 117}
{"id": "4", "title": "トップガン マーヴェリック", "overview": "トム・クルーズ主演の続編", "genre": ["アクション", "ドラマ"], "release_date": 1653696000, "rating": 8.3, "director": "ジョセフ・コシンスキー", "duration": 131}
EOF

# ドキュメント更新
curl -X PATCH "http://localhost:8108/collections/movies/documents/1" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "rating": 9.3,
    "updated_at": 1705392000
  }'

# ドキュメント削除
curl -X DELETE "http://localhost:8108/collections/movies/documents/1" \
  -H "X-TYPESENSE-API-KEY: xyz"

# ドキュメント取得
curl -X GET "http://localhost:8108/collections/movies/documents/1" \
  -H "X-TYPESENSE-API-KEY: xyz"

検索クエリの実装

# 基本検索
curl -X GET \
  "http://localhost:8108/collections/movies/documents/search?q=千と千尋&query_by=title,overview" \
  -H "X-TYPESENSE-API-KEY: xyz"

# 高度な検索オプション
curl -X GET \
  "http://localhost:8108/collections/movies/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=アニメ" \
  -d "query_by=title,overview,director" \
  -d "filter_by=rating:>=8.0 && genre:アニメーション" \
  -d "sort_by=rating:desc,release_date:desc" \
  -d "facet_by=genre,director" \
  -d "max_facet_values=10" \
  -d "per_page=20" \
  -d "page=1" \
  -d "highlight_full_fields=title,overview" \
  -d "snippet_threshold=15" \
  -d "typo_tokens_threshold=1" \
  -d "drop_tokens_threshold=2"

# ファセット検索
curl -X GET \
  "http://localhost:8108/collections/movies/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=*" \
  -d "query_by=title" \
  -d "facet_by=genre,director,release_date" \
  -d "max_facet_values=20" \
  -d "facet_query=genre:アニメ"

# 地理的検索
curl -X GET \
  "http://localhost:8108/collections/theaters/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=映画館" \
  -d "query_by=name,description" \
  -d "filter_by=location:(35.6762, 139.6503, 5 km)" \
  -d "sort_by=location(35.6762, 139.6503):asc"

# 複数クエリ検索(Multi-Search)
curl -X POST "http://localhost:8108/multi_search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "searches": [
      {
        "collection": "movies",
        "q": "アクション",
        "query_by": "title,overview",
        "filter_by": "rating:>=8.0"
      },
      {
        "collection": "books", 
        "q": "SF",
        "query_by": "title,description"
      }
    ]
  }'

# ファジー検索(タイポ許容)
curl -X GET \
  "http://localhost:8108/collections/movies/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=せんとちひろ" \
  -d "query_by=title,overview" \
  -d "typo_tokens_threshold=1" \
  -d "num_typos=2" \
  -d "min_len_1typo=4" \
  -d "min_len_2typo=7"

ベクトル検索とAI機能

# ベクトル検索用コレクション作成
curl -X POST "http://localhost:8108/collections" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "documents",
    "fields": [
      {"name": "id", "type": "string"},
      {"name": "title", "type": "string"},
      {"name": "content", "type": "string"},
      {"name": "embedding", "type": "float[]", "num_dim": 384}
    ]
  }'

# ベクトル検索実行
curl -X GET \
  "http://localhost:8108/collections/documents/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=*" \
  -d "vector_query=embedding:([0.1, 0.2, 0.3, ...], k:10)" \
  -d "per_page=10"

# ハイブリッド検索(テキスト+ベクトル)
curl -X GET \
  "http://localhost:8108/collections/documents/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=人工知能" \
  -d "query_by=title,content" \
  -d "vector_query=embedding:([0.15, 0.25, 0.35, ...], k:10)" \
  -d "rank_tokens_by=default_score" \
  -d "per_page=10"

# セマンティック検索用設定
curl -X POST "http://localhost:8108/collections" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "semantic_search",
    "fields": [
      {"name": "id", "type": "string"},
      {"name": "title", "type": "string"},
      {"name": "content", "type": "string"},
      {"name": "embedding", "type": "float[]", "num_dim": 768, "embed": {"from": ["title", "content"], "model_config": {"model_name": "ts/all-MiniLM-L12-v2"}}}
    ]
  }'

# 自動埋め込み生成での検索
curl -X GET \
  "http://localhost:8108/collections/semantic_search/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -G \
  -d "q=機械学習の応用" \
  -d "query_by=embedding" \
  -d "per_page=10"

パフォーマンス最適化

# コレクション統計確認
curl -X GET "http://localhost:8108/collections/movies" \
  -H "X-TYPESENSE-API-KEY: xyz"

# 検索パフォーマンス監視
curl -X GET "http://localhost:8108/stats.json" \
  -H "X-TYPESENSE-API-KEY: xyz"

# メモリ使用量確認
curl -X GET "http://localhost:8108/debug" \
  -H "X-TYPESENSE-API-KEY: xyz"

# 設定最適化(サーバー起動時)
./typesense-server \
  --data-dir=/data \
  --api-key=xyz \
  --enable-cors \
  --num-memory-shards=4 \
  --search-only-mode=false \
  --healthy-read-lag=1000 \
  --healthy-write-lag=1000 \
  --log-level=INFO \
  --max-memory-ratio=0.8

# キャッシュ設定
curl -X POST "http://localhost:8108/collections/movies/documents/search" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "アニメ",
    "query_by": "title,overview",
    "cache_ttl": 300
  }'

# インデックス最適化
curl -X POST "http://localhost:8108/collections/movies/compact" \
  -H "X-TYPESENSE-API-KEY: xyz"

SDKとフレームワーク連携

// JavaScript/Node.js SDK
import { Client as TypesenseClient } from 'typesense'

const client = new TypesenseClient({
  'nodes': [{
    'host': 'localhost',
    'port': '8108',
    'protocol': 'http'
  }],
  'apiKey': 'xyz',
  'connectionTimeoutSeconds': 2
})

// コレクション作成
const moviesSchema = {
  'name': 'movies',
  'fields': [
    {'name': 'id', 'type': 'string'},
    {'name': 'title', 'type': 'string'},
    {'name': 'overview', 'type': 'string'},
    {'name': 'genre', 'type': 'string[]'},
    {'name': 'rating', 'type': 'float'},
    {'name': 'release_date', 'type': 'int64'}
  ],
  'default_sorting_field': 'rating'
}

await client.collections().create(moviesSchema)

// ドキュメント追加
const documents = [
  {
    'id': '1',
    'title': '千と千尋の神隠し',
    'overview': '宮崎駿監督による日本のアニメーション映画',
    'genre': ['アニメーション', 'ファミリー', 'ファンタジー'],
    'rating': 9.2,
    'release_date': 995068800
  }
]

await client.collections('movies').documents().import(documents)

// 検索実行
const searchResults = await client
  .collections('movies')
  .documents()
  .search({
    'q': '千と千尋',
    'query_by': 'title,overview',
    'filter_by': 'rating:>=8.0',
    'sort_by': 'rating:desc'
  })

console.log(searchResults.hits)

// リアルタイム検索(インスタント検索)
const searchParams = {
  'q': '千',
  'query_by': 'title,overview',
  'typo_tokens_threshold': 1,
  'prefix': true,
  'per_page': 5
}

const instantResults = await client
  .collections('movies')
  .documents()
  .search(searchParams)
# Python SDK
import typesense

client = typesense.Client({
  'nodes': [{
    'host': 'localhost',
    'port': '8108',
    'protocol': 'http'
  }],
  'api_key': 'xyz',
  'connection_timeout_seconds': 2
})

# コレクション作成
movies_schema = {
  'name': 'movies',
  'fields': [
    {'name': 'id', 'type': 'string'},
    {'name': 'title', 'type': 'string'},
    {'name': 'overview', 'type': 'string'},
    {'name': 'genre', 'type': 'string[]'},
    {'name': 'rating', 'type': 'float'},
    {'name': 'release_date', 'type': 'int64'}
  ],
  'default_sorting_field': 'rating'
}

client.collections.create(movies_schema)

# ドキュメント追加
documents = [
  {
    'id': '1',
    'title': '千と千尋の神隠し',
    'overview': '宮崎駿監督による日本のアニメーション映画',
    'genre': ['アニメーション', 'ファミリー', 'ファンタジー'],
    'rating': 9.2,
    'release_date': 995068800
  }
]

client.collections['movies'].documents.import_(documents)

# 検索実行
search_results = client.collections['movies'].documents.search({
  'q': '千と千尋',
  'query_by': 'title,overview',
  'filter_by': 'rating:>=8.0',
  'sort_by': 'rating:desc'
})

print(search_results['hits'])

# 高度な検索
advanced_search = client.collections['movies'].documents.search({
  'q': 'アニメ',
  'query_by': 'title,overview,director',
  'filter_by': 'genre:アニメーション && rating:>=8.0',
  'facet_by': 'genre,director',
  'sort_by': 'rating:desc',
  'typo_tokens_threshold': 1,
  'prefix': True,
  'per_page': 10
})

高度な設定とクラスター構成

# 高可用性設定(3ノードクラスター)
# ノード1
./typesense-server \
  --data-dir=/data/node1 \
  --api-key=xyz \
  --peering-address=192.168.1.100 \
  --peering-port=8107 \
  --api-port=8108 \
  --nodes=192.168.1.100:8107:8108,192.168.1.101:8107:8108,192.168.1.102:8107:8108

# ノード2  
./typesense-server \
  --data-dir=/data/node2 \
  --api-key=xyz \
  --peering-address=192.168.1.101 \
  --peering-port=8107 \
  --api-port=8108 \
  --nodes=192.168.1.100:8107:8108,192.168.1.101:8107:8108,192.168.1.102:8107:8108

# ノード3
./typesense-server \
  --data-dir=/data/node3 \
  --api-key=xyz \
  --peering-address=192.168.1.102 \
  --peering-port=8107 \
  --api-port=8108 \
  --nodes=192.168.1.100:8107:8108,192.168.1.101:8107:8108,192.168.1.102:8107:8108

# レプリケーション設定
curl -X PATCH "http://localhost:8108/collections/movies" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -H "Content-Type: application/json" \
  -d '{
    "replication_factor": 3
  }'

# バックアップ作成
curl -X POST "http://localhost:8108/snapshots" \
  -H "X-TYPESENSE-API-KEY: xyz" \
  -d '{
    "snapshot_path": "/backups/snapshot_$(date +%Y%m%d_%H%M%S)"
  }'

# SSL/TLS設定
./typesense-server \
  --data-dir=/data \
  --api-key=xyz \
  --ssl-certificate=/path/to/certificate.pem \
  --ssl-certificate-key=/path/to/private.key \
  --enable-cors

Typesenseは、超高速性とシンプルさを重視した現代的な検索エンジンで、特にリアルタイム検索とタイポ許容機能において優れた性能を発揮します。オープンソースの利点を活かしながら、高性能な検索機能を低リソースで実現できる優れた選択肢です。