データベース

OpenSearch

概要

OpenSearchは、AWS主導で開発された分散検索・分析エンジンです。Elasticsearchからフォークされたオープンソースプロジェクトで、Apache 2.0ライセンスの下で完全に自由に利用できます。高速な全文検索、リアルタイム分析、機械学習機能を提供し、ログ分析、メトリクス監視、アプリケーション検索など幅広い用途に対応します。

詳細

OpenSearchは2021年にElasticsearchのライセンス変更に対応してAWSが開始したプロジェクトです。2024年後半にはLinux Foundationの下でOpenSearch Software Foundation(OSSF)が設立され、中立的なガバナンスの下で開発が進められています。Elasticsearch 7.10.2をベースとしながら、独自の機能拡張と最適化を継続的に実施しています。

主要特徴

  • 高速検索: 最大6倍の性能向上(初期バージョン比)
  • ベクトル検索: Facebook FAISS統合による高性能セマンティック検索
  • ハイブリッド検索: キーワード検索とベクトル検索の並列実行
  • OpenSearch Dashboards: データ可視化と管理UI
  • セキュリティ: 細粒度アクセス制御とマルチテナント対応
  • アラート: リアルタイム監視とアラート機能
  • Data Prepper: 強化されたデータ取り込みパイプライン
  • Remote-backed storage: クラウドストレージへの直接インデックス作成
  • Segment replication: 25%のデータ取り込み性能向上
  • SIMD最適化: ハードウェアアクセラレーション対応

アーキテクチャ

  • 分散システム: マルチノードクラスター対応
  • RESTful API: Elasticsearch互換API提供
  • プラグインシステム: 豊富な拡張機能
  • OpenSearch Dashboards: Kibana互換の可視化ツール
  • マルチテナンシー: テナント間の完全分離
  • クロスクラスター: 複数クラスター間の検索・レプリケーション

メリット・デメリット

メリット

  • 完全オープンソース: Apache 2.0ライセンスで商用制限なし
  • Elasticsearch互換: 既存システムからの移行が容易
  • 高性能: 最新最適化により大幅な性能向上
  • 豊富な機能: 検索、分析、可視化、ML機能を統合
  • 強固なセキュリティ: エンタープライズレベルのセキュリティ機能
  • アクティブ開発: 1,400以上の貢献者による活発な開発
  • AWS統合: AWSサービスとの緊密な連携
  • コスト効率: クラウドストレージとの統合によるコスト削減

デメリット

  • 比較的新しい: Elasticsearchと比べエコシステムが発展途上
  • 移行複雑性: Elasticsearchからの移行時に一部機能差異
  • 学習コスト: 新機能や独自拡張の学習が必要
  • プラグイン制限: Elasticsearchの一部プラグインが利用不可
  • 企業サポート: 商用サポートはAWS経由に限定的

主要リンク

書き方の例

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

# Dockerで起動(シングルノード)
docker run -d \
  --name opensearch-node1 \
  -p 9200:9200 -p 9600:9600 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStrongPassword123!" \
  opensearchproject/opensearch:latest

# Docker Composeでクラスター構築
cat > docker-compose.yml << EOF
version: '3'
services:
  opensearch-node1:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node1
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStrongPassword123!"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data1:/usr/share/opensearch/data
    ports:
      - 9200:9200
      - 9600:9600
    networks:
      - opensearch-net

  opensearch-node2:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node2
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node2
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStrongPassword123!"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data2:/usr/share/opensearch/data
    networks:
      - opensearch-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: opensearch-dashboards
    ports:
      - 5601:5601
    expose:
      - "5601"
    environment:
      OPENSEARCH_HOSTS: '["https://opensearch-node1:9200","https://opensearch-node2:9200"]'
    networks:
      - opensearch-net

volumes:
  opensearch-data1:
  opensearch-data2:

networks:
  opensearch-net:
EOF

docker-compose up -d

# 起動確認
curl -k -u admin:MyStrongPassword123! https://localhost:9200

基本操作(CRUD)

# インデックス作成
curl -k -X PUT "https://localhost:9200/products" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "name": { "type": "text", "analyzer": "standard" },
        "brand": { "type": "keyword" },
        "category": { "type": "keyword" },
        "price": { "type": "double" },
        "description": { "type": "text" },
        "tags": { "type": "keyword" },
        "created_at": { "type": "date" }
      }
    }
  }'

# ドキュメント追加
curl -k -X POST "https://localhost:9200/products/_doc/1" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "iPhone 15",
    "brand": "Apple",
    "category": "スマートフォン",
    "price": 124800,
    "description": "最新のiPhone、A17 Proチップ搭載",
    "tags": ["smartphone", "apple", "mobile"],
    "created_at": "2024-01-15"
  }'

# バルクインサート
curl -k -X POST "https://localhost:9200/products/_bulk" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '
{"index": {"_id": "2"}}
{"name": "MacBook Pro", "brand": "Apple", "category": "ノートPC", "price": 248800, "description": "M3チップ搭載の高性能ノートパソコン", "tags": ["laptop", "apple"], "created_at": "2024-01-10"}
{"index": {"_id": "3"}}
{"name": "Galaxy S24", "brand": "Samsung", "category": "スマートフォン", "price": 98800, "description": "Android搭載の高性能スマートフォン", "tags": ["smartphone", "samsung", "android"], "created_at": "2024-01-20"}
'

# ドキュメント取得
curl -k -X GET "https://localhost:9200/products/_doc/1" \
  -u admin:MyStrongPassword123!

# ドキュメント更新
curl -k -X POST "https://localhost:9200/products/_update/1" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "doc": {
      "price": 119800,
      "description": "最新のiPhone、A17 Proチップ搭載(価格改定)"
    }
  }'

# ドキュメント削除
curl -k -X DELETE "https://localhost:9200/products/_doc/3" \
  -u admin:MyStrongPassword123!

検索クエリ

# シンプル検索
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "match": {
        "name": "iPhone"
      }
    }
  }'

# 複合検索
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "bool": {
        "must": [
          { "match": { "description": "高性能" } }
        ],
        "filter": [
          { "term": { "brand": "Apple" } },
          { "range": { "price": { "gte": 100000, "lte": 300000 } } }
        ]
      }
    },
    "sort": [
      { "price": { "order": "asc" } }
    ],
    "size": 10,
    "from": 0
  }'

# ファジー検索
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "fuzzy": {
        "name": {
          "value": "iPhon",
          "fuzziness": "AUTO"
        }
      }
    }
  }'

# ハイライト付き検索
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "multi_match": {
        "query": "Apple",
        "fields": ["name", "description"]
      }
    },
    "highlight": {
      "fields": {
        "name": {},
        "description": {}
      },
      "pre_tags": ["<mark>"],
      "post_tags": ["</mark>"]
    }
  }'

集約(Aggregations)

# 基本的な集約
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 0,
    "aggs": {
      "brands": {
        "terms": {
          "field": "brand",
          "size": 10
        }
      },
      "avg_price": {
        "avg": {
          "field": "price"
        }
      },
      "price_stats": {
        "stats": {
          "field": "price"
        }
      }
    }
  }'

# ネストした集約
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 0,
    "aggs": {
      "brands": {
        "terms": {
          "field": "brand"
        },
        "aggs": {
          "avg_price_per_brand": {
            "avg": {
              "field": "price"
            }
          },
          "categories": {
            "terms": {
              "field": "category"
            }
          }
        }
      }
    }
  }'

# 範囲集約
curl -k -X GET "https://localhost:9200/products/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 0,
    "aggs": {
      "price_ranges": {
        "range": {
          "field": "price",
          "ranges": [
            { "to": 50000 },
            { "from": 50000, "to": 150000 },
            { "from": 150000, "to": 300000 },
            { "from": 300000 }
          ]
        }
      }
    }
  }'

インデックス管理・最適化

# インデックス情報確認
curl -k -X GET "https://localhost:9200/products/_settings" \
  -u admin:MyStrongPassword123!

curl -k -X GET "https://localhost:9200/products/_mapping" \
  -u admin:MyStrongPassword123!

# インデックス統計
curl -k -X GET "https://localhost:9200/products/_stats" \
  -u admin:MyStrongPassword123!

# インデックステンプレート作成
curl -k -X PUT "https://localhost:9200/_index_template/products_template" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "index_patterns": ["products*"],
    "template": {
      "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 1,
        "refresh_interval": "5s"
      },
      "mappings": {
        "properties": {
          "name": { "type": "text", "analyzer": "standard" },
          "brand": { "type": "keyword" },
          "category": { "type": "keyword" },
          "price": { "type": "double" },
          "created_at": { "type": "date" }
        }
      }
    }
  }'

# インデックス最適化
curl -k -X POST "https://localhost:9200/products/_forcemerge?max_num_segments=1" \
  -u admin:MyStrongPassword123!

# インデックス再作成
curl -k -X POST "https://localhost:9200/_reindex" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "source": {
      "index": "products"
    },
    "dest": {
      "index": "products_v2"
    }
  }'

JavaScript Client

// OpenSearchクライアントの設定
import { Client } from '@opensearch-project/opensearch'

const client = new Client({
  node: 'https://localhost:9200',
  auth: {
    username: 'admin',
    password: 'MyStrongPassword123!'
  },
  ssl: {
    rejectUnauthorized: false // 開発環境のみ
  }
})

// インデックス作成
await client.indices.create({
  index: 'products',
  body: {
    settings: {
      number_of_shards: 1,
      number_of_replicas: 1
    },
    mappings: {
      properties: {
        name: { type: 'text' },
        brand: { type: 'keyword' },
        category: { type: 'keyword' },
        price: { type: 'double' },
        description: { type: 'text' }
      }
    }
  }
})

// ドキュメント追加
await client.index({
  index: 'products',
  id: '1',
  body: {
    name: 'iPhone 15',
    brand: 'Apple',
    category: 'スマートフォン',
    price: 124800,
    description: '最新のiPhone、A17 Proチップ搭載'
  }
})

// バルクインサート
const body = [
  { index: { _index: 'products', _id: '2' } },
  { name: 'MacBook Pro', brand: 'Apple', category: 'ノートPC', price: 248800 },
  { index: { _index: 'products', _id: '3' } },
  { name: 'Galaxy S24', brand: 'Samsung', category: 'スマートフォン', price: 98800 }
]

await client.bulk({ body })

// 検索実行
const searchResponse = await client.search({
  index: 'products',
  body: {
    query: {
      bool: {
        must: [
          { match: { description: '高性能' } }
        ],
        filter: [
          { term: { brand: 'Apple' } },
          { range: { price: { gte: 100000 } } }
        ]
      }
    },
    sort: [
      { price: { order: 'asc' } }
    ],
    size: 10
  }
})

console.log('検索結果:', searchResponse.body.hits.hits)

// 集約
const aggregationResponse = await client.search({
  index: 'products',
  body: {
    size: 0,
    aggs: {
      brands: {
        terms: { field: 'brand' }
      },
      avg_price: {
        avg: { field: 'price' }
      }
    }
  }
})

console.log('集約結果:', aggregationResponse.body.aggregations)

ベクトル検索・AI機能

# ベクトル検索用インデックス作成
curl -k -X PUT "https://localhost:9200/documents" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "settings": {
      "index": {
        "knn": true,
        "knn.algo_param.ef_search": 100
      }
    },
    "mappings": {
      "properties": {
        "title": { "type": "text" },
        "content": { "type": "text" },
        "vector_field": {
          "type": "knn_vector",
          "dimension": 768,
          "method": {
            "name": "hnsw",
            "space_type": "l2",
            "engine": "faiss",
            "parameters": {
              "ef_construction": 128,
              "m": 24
            }
          }
        }
      }
    }
  }'

# ベクトル付きドキュメント追加
curl -k -X POST "https://localhost:9200/documents/_doc/1" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "OpenSearchの使い方",
    "content": "OpenSearchは強力な検索エンジンです",
    "vector_field": [0.1, 0.2, 0.3, /* ... 768次元のベクトル */]
  }'

# k-NN検索
curl -k -X GET "https://localhost:9200/documents/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 5,
    "query": {
      "knn": {
        "vector_field": {
          "vector": [0.1, 0.2, 0.3, /* ... 768次元のクエリベクトル */],
          "k": 5
        }
      }
    }
  }'

# ハイブリッド検索(テキスト + ベクトル)
curl -k -X GET "https://localhost:9200/documents/_search" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 10,
    "query": {
      "hybrid": {
        "queries": [
          {
            "match": {
              "content": "検索エンジン"
            }
          },
          {
            "knn": {
              "vector_field": {
                "vector": [0.1, 0.2, 0.3, /* ... */],
                "k": 5
              }
            }
          }
        ]
      }
    }
  }'

セキュリティ設定

# ユーザー作成
curl -k -X PUT "https://localhost:9200/_plugins/_security/api/internalusers/developer" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "password": "DeveloperPassword123!",
    "opendistro_security_roles": ["readall"],
    "backend_roles": ["developer"],
    "attributes": {
      "department": "engineering"
    }
  }'

# ロール作成
curl -k -X PUT "https://localhost:9200/_plugins/_security/api/roles/products_reader" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "cluster_permissions": ["cluster_composite_ops"],
    "index_permissions": [
      {
        "index_patterns": ["products*"],
        "allowed_actions": ["read", "search"]
      }
    ]
  }'

# テナント作成
curl -k -X PUT "https://localhost:9200/_plugins/_security/api/tenants/development" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "description": "Development environment tenant"
  }'

# ロールマッピング
curl -k -X PUT "https://localhost:9200/_plugins/_security/api/rolesmapping/products_reader" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "backend_roles": ["developer"],
    "users": ["developer"]
  }'

監視・アラート

# モニター作成
curl -k -X POST "https://localhost:9200/_plugins/_alerting/monitors" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "monitor",
    "name": "High Price Products Monitor",
    "enabled": true,
    "schedule": {
      "period": {
        "interval": 5,
        "unit": "MINUTES"
      }
    },
    "inputs": [
      {
        "search": {
          "indices": ["products"],
          "query": {
            "query": {
              "range": {
                "price": {
                  "gte": 200000
                }
              }
            }
          }
        }
      }
    ],
    "triggers": [
      {
        "name": "High price trigger",
        "severity": "1",
        "condition": {
          "script": {
            "source": "ctx.results[0].hits.total.value > 5"
          }
        },
        "actions": [
          {
            "name": "Send email",
            "destination_id": "email_destination_id",
            "message_template": {
              "source": "発見された高額商品: {{ctx.results.0.hits.total.value}}件"
            }
          }
        ]
      }
    ]
  }'

# 宛先設定(メール)
curl -k -X POST "https://localhost:9200/_plugins/_alerting/destinations" \
  -u admin:MyStrongPassword123! \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "email-destination",
    "type": "email",
    "email": {
      "email_account_id": "default_email",
      "recipients": ["[email protected]"],
      "subject": "OpenSearch Alert",
      "message": "アラートが発生しました"
    }
  }'

本番環境設定

# opensearch.yml
cluster.name: production-cluster
node.name: opensearch-node-1
path.data: /var/lib/opensearch
path.logs: /var/log/opensearch

network.host: 0.0.0.0
http.port: 9200
transport.port: 9300

discovery.seed_hosts: ["opensearch-node-1", "opensearch-node-2", "opensearch-node-3"]
cluster.initial_cluster_manager_nodes: ["opensearch-node-1", "opensearch-node-2", "opensearch-node-3"]

# メモリ設定
bootstrap.memory_lock: true

# セキュリティ設定
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: /path/to/cert.pem
plugins.security.ssl.http.pemkey_filepath: /path/to/key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: /path/to/ca.pem

# パフォーマンス設定
indices.memory.index_buffer_size: 20%
indices.memory.min_index_buffer_size: 96mb
thread_pool.search.queue_size: 10000
thread_pool.write.queue_size: 1000

# ロギング設定
logger.org.opensearch: INFO
logger.org.opensearch.index.search.slowlog: WARN, index_search_slow_log_file
logger.org.opensearch.index.indexing.slowlog: WARN, index_indexing_slow_log_file